---
title: "Authentication"
description: "JWT tokens, API keys, and OAuth2 authentication for AINative Studio"
canonical: "https://docs.ainative.studio/docs/getting-started/authentication"
last-updated: "2026-09-23T01:51:08.820Z"
---

# Authentication

Source: https://docs.ainative.studio/docs/getting-started/authentication

> JWT tokens, API keys, and OAuth2 authentication for AINative Studio

**Version**: 1.0
**Last Updated**: 2026-02-03
**Security Level**: Production-Ready

---

## Table of Contents

1. [Overview](#overview)
2. [Authentication Methods](#authentication-methods)
3. [JWT Token Authentication](#jwt-token-authentication)
4. [API Key Authentication](#api-key-authentication)
5. [OAuth2 Integration](#oauth2-integration)
6. [Security Best Practices](#security-best-practices)
7. [Token Management](#token-management)
8. [Troubleshooting](#troubleshooting)

---

## Overview

AINative Studio uses industry-standard authentication mechanisms to secure API access. We support multiple authentication methods to accommodate different use cases:

- **JWT Bearer Tokens**: For user-facing applications and session management
- **API Keys**: For server-to-server communication and long-lived integrations
- **OAuth2 / Sign in with AINative**: For third-party app integrations — let your users authenticate with their AINative account

### Authentication Flow Diagram

```
User Registration/Login
         │
         ▼
   Generate JWT Token
         │
         ├──────> Store in localStorage (web)
         ├──────> Store in keychain (mobile)
         └──────> Store in memory (server)
         │
         ▼
   API Request with Token
         │
         ▼
   Backend Validates Token
         │
         ├──────> Valid ──────> Process Request
         │
         └──────> Invalid ────> 401 Unauthorized
```

---

## Authentication Methods

### Comparison Table

| Method | Use Case | Expiration | Revocable | Best For |
|--------|----------|------------|-----------|----------|
| JWT Token | User sessions | 8 hours | Yes (blacklist) | Web/mobile apps |
| API Key | Service integration | 90 days (configurable) | Yes | Server-to-server |
| OAuth2 | Third-party auth | N/A | Yes | Social login |

---

## JWT Token Authentication

### How JWT Tokens Work

JSON Web Tokens (JWT) are self-contained tokens that include:

1. **Header**: Algorithm and token type
2. **Payload**: User claims (id, email, role, expiration)
3. **Signature**: Cryptographic signature to verify authenticity

### Token Structure

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9           <- Header
.eyJzdWIiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJyb2xlIjoiVVNFUiIsImV4cCI6MTcwNDExMDQwMH0  <- Payload
.xyz123abc                                       <- Signature
```

### Decoded Payload

```json
{
  "sub": "550e8400-e29b-41d4-a716-446655440000",  // User ID
  "email": "user@example.com",
  "role": "USER",
  "exp": 1704110400,  // Expiration timestamp
  "iat": 1704082400   // Issued at timestamp
}
```

### Obtaining a JWT Token

#### Method 1: Email/Password Registration

```bash
POST /api/v1/auth/register
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "SecurePass123!",
  "full_name": "John Doe"
}
```

**Response:**
```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 28800
}
```

#### Method 2: Email/Password Login

```bash
POST /api/v1/auth/login
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "SecurePass123!"
}
```

**Response:**
```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 28800
}
```

### Using JWT Tokens

Include the token in the `Authorization` header:

```bash
GET /api/v1/auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

### Python Example

```python
import requests

## Login and get token
login_response = requests.post(
    "https://api.ainative.studio/api/v1/auth/login",
    json={
        "email": "user@example.com",
        "password": "SecurePass123!"
    }
)

token = login_response.json()["access_token"]

## Use token for authenticated requests
headers = {"Authorization": f"Bearer {token}"}

user_response = requests.get(
    "https://api.ainative.studio/api/v1/auth/me",
    headers=headers
)

print(user_response.json())
```

### JavaScript Example

```javascript
// Login and get token
const loginResponse = await fetch(
  'https://api.ainative.studio/api/v1/auth/login',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: 'user@example.com',
      password: 'SecurePass123!'
    })
  }
);

const { access_token } = await loginResponse.json();

// Use token for authenticated requests
const userResponse = await fetch(
  'https://api.ainative.studio/api/v1/auth/me',
  {
    headers: { 'Authorization': `Bearer ${access_token}` }
  }
);

const userData = await userResponse.json();
console.log(userData);
```

### Token Refresh

Tokens expire after 8 hours. Refresh before expiration:

```bash
POST /api/v1/auth/refresh
Authorization: Bearer <current_token>
```

**Response:**
```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 28800
}
```

### Automatic Token Refresh (Python)

```python
import requests
from datetime import datetime, timedelta

class AuthClient:
    def __init__(self, email, password):
        self.email = email
        self.password = password
        self.token = None
        self.token_expires_at = None
        self.login()

    def login(self):
        response = requests.post(
            "https://api.ainative.studio/api/v1/auth/login",
            json={"email": self.email, "password": self.password}
        )
        data = response.json()
        self.token = data["access_token"]
        self.token_expires_at = datetime.now() + timedelta(seconds=data["expires_in"])

    def get_token(self):
        # Refresh 5 minutes before expiration
        if datetime.now() >= self.token_expires_at - timedelta(minutes=5):
            self.refresh_token()
        return self.token

    def refresh_token(self):
        response = requests.post(
            "https://api.ainative.studio/api/v1/auth/refresh",
            headers={"Authorization": f"Bearer {self.token}"}
        )
        data = response.json()
        self.token = data["access_token"]
        self.token_expires_at = datetime.now() + timedelta(seconds=data["expires_in"])

    def make_request(self, url, **kwargs):
        headers = kwargs.get("headers", {})
        headers["Authorization"] = f"Bearer {self.get_token()}"
        kwargs["headers"] = headers
        return requests.get(url, **kwargs)

## Usage
client = AuthClient("user@example.com", "SecurePass123!")
response = client.make_request("https://api.ainative.studio/api/v1/database/projects")
print(response.json())
```

---

## API Key Authentication

### When to Use API Keys

Use API keys for:

- Server-to-server communication
- CI/CD pipelines
- Long-running services
- Background jobs
- Webhook endpoints

**Do NOT use API keys for:**
- Client-side JavaScript (keys will be exposed)
- Mobile apps (use JWT tokens instead)
- User-facing applications

### Creating an API Key

```bash
POST /api/v1/api-keys
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json

{
  "name": "Production Server Key",
  "expires_in_days": 90
}
```

**Response:**
```json
{
  "id": "key_550e8400-e29b-41d4-a716-446655440000",
  "api_key": "sk_live_EXAMPLE_not_a_real_key",
  "name": "Production Server Key",
  "prefix": "sk_live",
  "expires_at": "2026-05-03T00:00:00Z",
  "created_at": "2026-02-03T12:00:00Z"
}
```

**Critical**: Save the `api_key` value immediately - it's only shown once!

### API Key Formats

| Prefix | Environment | Usage |
|--------|-------------|-------|
| `sk_` / `sk_live_` | Production | Authenticated users — permanent keys scoped to a project |
| `sk_test_` | Testing | Sandbox environment |
| `tmp_` | Temporary | Auto-provisioned for unauthenticated instant-db users — expires in 72 hours |
| `zdb_live_` | Claimed | Issued when a `tmp_` project is claimed to a real account |

### Using API Keys

#### Option 1: X-API-Key Header (Recommended)

```bash
GET /api/v1/database/projects
X-API-Key: sk_live_EXAMPLE_not_a_real_key
```

#### Option 2: Authorization Header

```bash
GET /api/v1/database/projects
Authorization: Bearer sk_live_EXAMPLE_not_a_real_key
```

### Python Example with API Key

```python
import requests
import os

API_KEY = os.getenv("AINATIVE_API_KEY")

headers = {"X-API-Key": API_KEY}

response = requests.get(
    "https://api.ainative.studio/api/v1/database/projects",
    headers=headers
)

print(response.json())
```

### Verifying an API Key

Before running a paid operation (for example, chat completions), you can pre-check a key's status, tier, remaining credits, and capabilities. This is the **canonical** API-key verification endpoint — apps and agents should use it instead of rolling their own key validation.

```bash
POST /api/v1/public/api-keys/verify
X-API-Key: sk_live_EXAMPLE_not_a_real_key
```

The key is read from the request headers — pass it as `X-API-Key` or as `Authorization: Bearer sk_...`. No request body is required.

**Response:**
```json
{
  "status": "READY",
  "message": null,
  "email": "user@example.com",
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "organization_id": "0f244da9-...",
  "organization_name": "Acme Inc",
  "tier": "pro",
  "user_plan": "pro",
  "subscription": {
    "tier": "PRO",
    "status": "active",
    "credits_total": 100000,
    "credits_remaining": 84568
  },
  "capabilities": {
    "chat_completions": true,
    "embeddings": true,
    "zerodb": true,
    "file_upload": true
  }
}
```

The `status` field tells you whether the key can be used:

| `status` | Meaning |
|----------|---------|
| `READY` | Key is valid and can perform all operations for its tier |
| `insufficient_credits` | Paid tier but credits are exhausted — upgrade or wait for reset |
| `trial_active` | Temporary Instant DB key — register to keep data and unlock chat |
| `trial_expired` | Instant DB trial expired (72h) — claim the account before data is deleted |
| `upgrade_required` | Free tier — a paid plan is required for chat completions |

An invalid or expired key returns `401 Unauthorized` (the request never reaches the verify logic).

### Managing API Keys

#### List All Your API Keys

```bash
GET /api/v1/api-keys
Authorization: Bearer YOUR_JWT_TOKEN
```

**Response:**
```json
[
  {
    "id": "key_123",
    "name": "Production Server Key",
    "prefix": "sk_live",
    "is_active": true,
    "last_used_at": "2026-02-03T11:45:00Z",
    "usage_count": 15432,
    "expires_at": "2026-05-03T00:00:00Z",
    "created_at": "2026-02-03T12:00:00Z"
  }
]
```

#### Revoke an API Key

```bash
DELETE /api/v1/api-keys/{key_id}
Authorization: Bearer YOUR_JWT_TOKEN
```

**Response:**
```json
{
  "message": "API key revoked successfully"
}
```

### API Key Rotation Strategy

Best practice: Rotate keys every 90 days

```python
import requests
from datetime import datetime, timedelta

def rotate_api_key(old_key_id: str, jwt_token: str):
    """
    Rotate API key: create new key, update services, revoke old key
    """
    # Step 1: Create new key
    create_response = requests.post(
        "https://api.ainative.studio/api/v1/api-keys",
        headers={"Authorization": f"Bearer {jwt_token}"},
        json={"name": f"Rotated Key {datetime.now()}", "expires_in_days": 90}
    )
    new_key = create_response.json()["api_key"]

    print(f"New API Key: {new_key}")
    print("Update your services with this new key, then press Enter to revoke the old key...")
    input()

    # Step 2: Revoke old key
    revoke_response = requests.delete(
        f"https://api.ainative.studio/api/v1/api-keys/{old_key_id}",
        headers={"Authorization": f"Bearer {jwt_token}"}
    )

    print("Old key revoked successfully")
    return new_key
```

---

## OAuth2 Integration

### Supported OAuth Providers

- **AINative (Sign in with AINative)** — OAuth 2.1 + PKCE for third-party apps
- GitHub
- LinkedIn

---

### Sign in with AINative

Allow your users to sign in with their AINative account. AINative implements OAuth 2.1 Authorization Code flow with PKCE and publishes a standard OIDC discovery document.

#### OIDC Discovery

```
GET https://api.ainative.studio/.well-known/openid-configuration
```

OIDC-compatible libraries (NextAuth.js, Auth.js, Passport.js) can auto-configure from this URL alone.

#### Step 1: Register Your OAuth Client

Register once to receive a `client_id` and `client_secret`.

```bash
curl -X POST https://api.ainative.studio/oauth/clients/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "Your App Name",
    "redirect_uris": ["https://yourapp.com/api/auth/callback/ainative"],
    "grant_types": ["authorization_code"],
    "scope": "openid profile email",
    "token_endpoint_auth_method": "client_secret_basic"
  }'
```

```json
{
  "client_id": "550e8400-e29b-41d4-a716-446655440000",
  "client_secret": "your-secret-here",
  "redirect_uris": ["https://yourapp.com/api/auth/callback/ainative"],
  "scope": "openid profile email"
}
```

Store both values in environment variables. Never expose `client_secret` in client-side code.

#### Step 2: Generate PKCE Values (server-side)

**Python:**
```python
import secrets, hashlib, base64

code_verifier = secrets.token_urlsafe(64)
digest = hashlib.sha256(code_verifier.encode()).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
```

**TypeScript:**
```typescript
import crypto from "crypto";

const codeVerifier = crypto.randomBytes(64).toString("base64url");
const codeChallenge = crypto
  .createHash("sha256")
  .update(codeVerifier)
  .digest("base64url");
```

#### Step 3: Redirect to AINative

```
GET https://api.ainative.studio/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/api/auth/callback/ainative
  &scope=openid%20profile%20email
  &state=RANDOM_CSRF_TOKEN
  &code_challenge=YOUR_CODE_CHALLENGE
  &code_challenge_method=S256
```

#### Step 4: Exchange Code for Token

```bash
curl -X POST https://api.ainative.studio/api/v1/oauth/token \
  -u "CLIENT_ID:CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=AUTH_CODE&redirect_uri=YOUR_REDIRECT_URI&code_verifier=CODE_VERIFIER"
```

```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 28800
}
```

#### Step 5: Fetch User Info

```bash
curl https://api.ainative.studio/oauth/userinfo \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

```json
{
  "sub": "9492c823-e54e-4a5b-915e-593781d6b805",
  "email": "user@example.com",
  "email_verified": true,
  "name": "Jane Developer",
  "given_name": "Jane",
  "family_name": "Developer",
  "plan": "pro"
}
```

Use `sub` as your stable user identifier — it never changes.

#### Available Scopes

| Scope | Description |
|-------|-------------|
| `openid` | Required for OIDC — enables `sub` claim |
| `profile` | `name`, `given_name`, `family_name` |
| `email` | `email`, `email_verified` |
| `memory:read` | Read from the user's ZeroDB memory |
| `memory:write` | Write to the user's ZeroDB memory |
| `vector:search` | Query the user's vector store |
| `vector:write` | Index documents into the user's vector store |
| `agent:read` | List and inspect the user's agents |
| `agent:write` | Create and deploy agents on the user's behalf |

#### NextAuth.js Integration

```typescript
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";

export const { handlers, auth } = NextAuth({
  providers: [
    {
      id: "ainative",
      name: "AINative",
      type: "oauth",
      issuer: "https://api.ainative.studio",
      wellKnown: "https://api.ainative.studio/.well-known/openid-configuration",
      authorization: { params: { scope: "openid profile email" } },
      clientId: process.env.AINATIVE_CLIENT_ID,
      clientSecret: process.env.AINATIVE_CLIENT_SECRET,
      profile(profile) {
        return {
          id: profile.sub,
          name: profile.name,
          email: profile.email,
        };
      },
    },
  ],
});
```

#### Endpoint Reference

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/oauth/clients/register` | POST | Register OAuth client (RFC 7591) |
| `/oauth/authorize` | GET | Authorization + login page |
| `/api/v1/oauth/token` | POST | Exchange code for access token |
| `/oauth/userinfo` | GET | OIDC UserInfo claims |
| `/.well-known/openid-configuration` | GET | OIDC discovery document |

---

### GitHub OAuth Flow

#### Step 1: Redirect User to GitHub

```
https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&scope=user:email
```

#### Step 2: Exchange Code for Token

```bash
POST /api/v1/auth/github/callback
Content-Type: application/json

{
  "code": "github_authorization_code",
  "redirect_uri": "https://yourapp.com/callback"
}
```

**Response:**
```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "user": {
    "id": "github_user_id",
    "email": "user@example.com",
    "name": "John Doe",
    "avatar_url": "https://avatars.githubusercontent.com/..."
  }
}
```

### LinkedIn OAuth Flow

#### Step 1: Redirect User to LinkedIn

```
https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&scope=openid%20profile%20email
```

#### Step 2: Exchange Code for Token

```bash
POST /api/v1/auth/linkedin/callback
Content-Type: application/json

{
  "code": "linkedin_authorization_code",
  "redirect_uri": "https://yourapp.com/callback"
}
```

**Response:**
```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 28800,
  "user": {
    "id": "linkedin_user_id",
    "email": "user@example.com",
    "name": "John Doe",
    "picture": "https://media.licdn.com/..."
  }
}
```

---

## Security Best Practices

### 1. Token Storage

| Platform | Recommended Storage |
|----------|-------------------|
| Web (Browser) | localStorage or sessionStorage (XSS risk), prefer secure HttpOnly cookies |
| Mobile (iOS/Android) | Keychain (iOS) or KeyStore (Android) |
| Server | Environment variables or secrets manager |
| Desktop App | OS-specific secure storage |

### 2. Never Commit Credentials

```bash
## .gitignore
.env
.env.local
*.pem
*.key
config/secrets.json
```

### 3. Use Environment Variables

```python
## ❌ WRONG - Hardcoded
API_KEY = "sk_live_EXAMPLE_not_a_real_key"

## ✅ CORRECT - Environment variable
import os
API_KEY = os.getenv("AINATIVE_API_KEY")
```

### 4. Implement Token Expiration Handling

```python
import requests

def make_authenticated_request(url, token):
    try:
        response = requests.get(
            url,
            headers={"Authorization": f"Bearer {token}"}
        )
        response.raise_for_status()
        return response.json()
    except requests.exceptions.HTTPError as e:
        if e.response.status_code == 401:
            # Token expired - refresh or re-login
            new_token = refresh_token(token)
            return make_authenticated_request(url, new_token)
        raise
```

### 5. Use HTTPS Only

```python
## ❌ WRONG
base_url = "http://api.ainative.studio"

## ✅ CORRECT
base_url = "https://api.ainative.studio"
```

### 6. Implement Rate Limiting

```python
from time import sleep
import requests

class RateLimitedClient:
    def __init__(self, api_key, requests_per_minute=60):
        self.api_key = api_key
        self.requests_per_minute = requests_per_minute
        self.request_times = []

    def make_request(self, url, **kwargs):
        # Remove old requests from tracking
        current_time = time.time()
        self.request_times = [
            t for t in self.request_times
            if current_time - t < 60
        ]

        # Wait if rate limit reached
        if len(self.request_times) >= self.requests_per_minute:
            sleep_time = 60 - (current_time - self.request_times[0])
            sleep(sleep_time)

        # Make request
        headers = kwargs.get("headers", {})
        headers["X-API-Key"] = self.api_key
        kwargs["headers"] = headers

        response = requests.get(url, **kwargs)
        self.request_times.append(time.time())

        return response
```

### 7. Validate SSL Certificates

```python
import requests

## ✅ Always verify SSL (default)
response = requests.get(url, verify=True)

## ❌ NEVER do this in production
response = requests.get(url, verify=False)  # Insecure!
```

### 8. Password Requirements

When creating user accounts:

- Minimum 8 characters
- At least one uppercase letter (A-Z)
- At least one lowercase letter (a-z)
- At least one number (0-9)
- At least one special character (!@#$%^&*)

---

## Token Management

### Token Blacklist

Logout invalidates tokens by adding them to a blacklist:

```bash
POST /api/v1/auth/logout
Authorization: Bearer <token>
```

### Password Reset Flow

#### Step 1: Request Reset

```bash
POST /api/v1/auth/forgot-password
Content-Type: application/json

{
  "email": "user@example.com"
}
```

#### Step 2: Reset with Token from Email

```bash
POST /api/v1/auth/reset-password
Content-Type: application/json

{
  "token": "reset_token_from_email",
  "new_password": "NewSecurePass456!"
}
```

### Change Password

```bash
POST /api/v1/auth/change-password
Authorization: Bearer <token>
Content-Type: application/json

{
  "current_password": "OldPass123!",
  "new_password": "NewPass456!"
}
```

---

## Troubleshooting

### Common Issues

#### 1. 401 Unauthorized

**Cause**: Invalid or expired token

**Solution**:
```python
## Refresh token or re-login
response = requests.post(
    "https://api.ainative.studio/api/v1/auth/refresh",
    headers={"Authorization": f"Bearer {token}"}
)
new_token = response.json()["access_token"]
```

#### 2. 403 Forbidden

**Cause**: Insufficient permissions or wrong API key for the workspace

**Solution**: Check the error response — it includes `workspace_name` and `suggested_api_key` to help you switch to the correct key. This often happens when using a `tmp_` key after signing up; use your project-scoped `sk_` or `zdb_live_` key instead.

#### 3. Token Not Working After Logout

**Cause**: Token is blacklisted

**Solution**: Generate a new token by logging in again

#### 4. API Key Invalid

**Cause**: Key was revoked or expired

**Solution**: Create a new API key

---

## Identity Merge

If you started with a temporary instant-db (`tmp_` key) and later created a real account, merge the temp identity into your real account:

```bash
POST /api/v1/auth/merge-identity
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json

{
  "temp_api_key": "tmp_your_temp_key..."
}
```

This transfers all projects and API keys from the temp identity to your authenticated account and deactivates the temp user. All data is preserved.

---

## Auto-Provisioning (Stripe)

When a user signs up through Stripe checkout, AINative auto-provisions their account:

- Creates the user with a bcrypt-hashed temporary password
- Creates a **Default Workspace** and assigns the USER role
- Sends a set-password email and schedules a 24h welcome email
- Syncs to CRM

When the user later registers with `POST /api/v1/auth/register` and includes the `stripe_session_id`, the existing auto-provisioned account is detected and updated — no duplicate account is created.

---

## Testing Authentication

### Test Endpoints

```bash
## Test authentication status
GET /api/v1/test-auth/auth-status

## Create test user
POST /api/v1/test-auth/create-test-user
Content-Type: application/json
{
  "email": "test@example.com",
  "password": "TestPass123!"
}

## Test login
POST /api/v1/test-auth/test-login
Content-Type: application/json
{
  "email": "test@example.com",
  "password": "TestPass123!"
}
```

---

## Additional Resources

- [API Reference](/docs/api/overview)
- [Quick Start Guide](/docs/getting-started/quick-start)
- [Troubleshooting Guide](/docs/guides/troubleshooting)
- [Security Best Practices](/docs/getting-started/authentication#security-best-practices)

---

**Last Updated**: 2026-02-03
**Version**: 1.0
**Security Review**: Completed

Refs #1015
