# API Reference

All API responses follow standardized format:

```json
{
  "app": "AI Dungeon Master",
  "version": "1.0.0",
  "status": 200,
  "timestamp": "2025-11-14T12:00:00Z",
  "request_id": "optional-request-id",
  "result": {},
  "error": null,
  "meta": {}
}
```

**Base URL:** `/api/v1`

---

## Authentication

Authentication handled by Appwrite with HTTP-only cookies. Sessions are stored in `coc_session` cookie.

**Cookie Configuration:**
- **Name:** `coc_session`
- **HTTP-only:** true (JavaScript cannot access)
- **Secure:** true (HTTPS only in production)
- **SameSite:** Lax (CSRF protection)
- **Duration (normal):** 24 hours
- **Duration (remember me):** 30 days

**Session Caching:**
Sessions are cached in Redis (db 2) to reduce Appwrite API calls by ~90%. Cache TTL is 5 minutes. Sessions are explicitly invalidated on logout and password change.

### Register

| | |
|---|---|
| **Endpoint** | `POST /api/v1/auth/register` |
| **Description** | Create new user account with email verification |
| **Auth Required** | No |

**Request Body:**
```json
{
  "email": "player@example.com",
  "password": "SecurePass123!",
  "name": "Adventurer"
}
```

**Response (201 Created):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 201,
  "timestamp": "2025-11-14T12:00:00Z",
  "result": {
    "user": {
      "id": "user_id_123",
      "email": "player@example.com",
      "name": "Adventurer",
      "email_verified": false,
      "tier": "free",
      "created_at": "2025-11-14T12:00:00Z"
    },
    "message": "Registration successful. Please check your email to verify your account."
  }
}
```

### Login

| | |
|---|---|
| **Endpoint** | `POST /api/v1/auth/login` |
| **Description** | User login with session cookie |
| **Auth Required** | No |

**Request Body:**
```json
{
  "email": "player@example.com",
  "password": "SecurePass123!",
  "remember_me": true
}
```

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-14T12:00:00Z",
  "result": {
    "user": {
      "id": "user_id_123",
      "email": "player@example.com",
      "name": "Adventurer",
      "email_verified": true,
      "tier": "free"
    },
    "message": "Login successful"
  }
}
```

**Set-Cookie Header:**
```
Set-Cookie: coc_session=<session_token>; HttpOnly; Secure; SameSite=Lax; Max-Age=2592000; Path=/
```

### Logout

| | |
|---|---|
| **Endpoint** | `POST /api/v1/auth/logout` |
| **Description** | Logout current session and clear cookie |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-14T12:00:00Z",
  "result": {
    "message": "Logout successful"
  }
}
```

### Get Current User

| | |
|---|---|
| **Endpoint** | `GET /api/v1/auth/me` |
| **Description** | Get current authenticated user's data |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-14T12:00:00Z",
  "result": {
    "id": "user_id_123",
    "email": "player@example.com",
    "name": "Adventurer",
    "email_verified": true,
    "tier": "premium"
  }
}
```

### Verify Email

| | |
|---|---|
| **Endpoint** | `GET /api/v1/auth/verify-email` |
| **Description** | Verify user email address |
| **Auth Required** | No |

**Query Parameters:**
- `userId` - User ID from verification email
- `secret` - Verification secret from email

**Success:** Redirects to `/auth/login?verified=true`
**Error:** Redirects to `/auth/login?verified=false`

### Forgot Password

| | |
|---|---|
| **Endpoint** | `POST /api/v1/auth/forgot-password` |
| **Description** | Request password reset email |
| **Auth Required** | No |

**Request Body:**
```json
{
  "email": "player@example.com"
}
```

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-14T12:00:00Z",
  "result": {
    "message": "If an account exists with this email, you will receive a password reset link shortly."
  }
}
```

### Reset Password (Display Form)

| | |
|---|---|
| **Endpoint** | `GET /api/v1/auth/reset-password` |
| **Description** | Display password reset form |
| **Auth Required** | No |

**Query Parameters:**
- `userId` - User ID from reset email
- `secret` - Reset secret from email

**Success:** Renders password reset form

### Reset Password (Submit)

| | |
|---|---|
| **Endpoint** | `POST /api/v1/auth/reset-password` |
| **Description** | Submit new password |
| **Auth Required** | No |

**Request Body:**
```json
{
  "user_id": "user_id_123",
  "secret": "reset_secret",
  "password": "NewSecurePass123!"
}
```

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-14T12:00:00Z",
  "result": {
    "message": "Password reset successful. You can now log in with your new password."
  }
}
```

---

## Characters

### List Characters

| | |
|---|---|
| **Endpoint** | `GET /api/v1/characters` |
| **Description** | Get all characters for current user with tier information |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": {
    "characters": [
      {
        "character_id": "char_123",
        "name": "Thorin Ironheart",
        "class": "vanguard",
        "class_name": "Vanguard",
        "level": 5,
        "experience": 250,
        "gold": 1000,
        "current_location": "forgotten_crypt",
        "origin": "soul_revenant"
      }
    ],
    "count": 1,
    "tier": "free",
    "limit": 1
  }
}
```

### Get Character

| | |
|---|---|
| **Endpoint** | `GET /api/v1/characters/<id>` |
| **Description** | Get full character details including inventory, equipment, and skills |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": {
    "character_id": "char_123",
    "user_id": "user_456",
    "name": "Thorin Ironheart",
    "player_class": {
      "class_id": "vanguard",
      "name": "Vanguard",
      "description": "A seasoned warrior...",
      "base_stats": {
        "strength": 14,
        "dexterity": 10,
        "constitution": 14,
        "intelligence": 8,
        "wisdom": 10,
        "charisma": 9
      },
      "skill_trees": [
        {
          "tree_id": "shield_bearer",
          "name": "Shield Bearer",
          "description": "Defensive tanking specialization",
          "nodes": []
        }
      ],
      "starting_equipment": ["rusty_sword"],
      "starting_abilities": ["basic_attack"]
    },
    "origin": {
      "id": "soul_revenant",
      "name": "Soul Revenant",
      "description": "Returned from death...",
      "starting_location": {
        "id": "forgotten_crypt",
        "name": "The Forgotten Crypt",
        "region": "The Deadlands",
        "description": "An ancient burial site..."
      },
      "narrative_hooks": [],
      "starting_bonus": {}
    },
    "level": 5,
    "experience": 250,
    "base_stats": {
      "strength": 14,
      "dexterity": 10,
      "constitution": 14,
      "intelligence": 8,
      "wisdom": 10,
      "charisma": 9
    },
    "unlocked_skills": ["shield_bash", "iron_defense"],
    "inventory": [],
    "equipped": {},
    "gold": 1000,
    "active_quests": [],
    "discovered_locations": ["forgotten_crypt"],
    "current_location": "forgotten_crypt"
  }
}
```

**Error Response (404 Not Found):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 404,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": null,
  "error": {
    "code": "NOT_FOUND",
    "message": "Character not found: char_999",
    "details": {}
  }
}
```

### Create Character

| | |
|---|---|
| **Endpoint** | `POST /api/v1/characters` |
| **Description** | Create new character (validates tier limits) |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "name": "Thorin Ironheart",
  "class_id": "vanguard",
  "origin_id": "soul_revenant"
}
```

**Validation Rules:**
- **name**: 2-50 characters, letters/numbers/spaces/hyphens/apostrophes only
- **class_id**: Must be one of: vanguard, assassin, arcanist, luminary, wildstrider, oathkeeper, necromancer, lorekeeper
- **origin_id**: Must be one of: soul_revenant, memory_thief, shadow_apprentice, escaped_captive

**Response (201 Created):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 201,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": {
    "character_id": "char_123",
    "name": "Thorin Ironheart",
    "class": "vanguard",
    "class_name": "Vanguard",
    "origin": "soul_revenant",
    "origin_name": "Soul Revenant",
    "level": 1,
    "gold": 0,
    "current_location": "forgotten_crypt",
    "message": "Character created successfully"
  }
}
```

**Error Response (400 Bad Request - Limit Exceeded):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 400,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": null,
  "error": {
    "code": "CHARACTER_LIMIT_EXCEEDED",
    "message": "Character limit reached for free tier (1/1). Upgrade your subscription to create more characters.",
    "details": {}
  }
}
```

**Error Response (400 Bad Request - Validation Error):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 400,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": null,
  "error": {
    "code": "INVALID_INPUT",
    "message": "Validation failed",
    "details": {
      "name": "Character name must be at least 2 characters",
      "class_id": "Invalid class ID. Must be one of: vanguard, assassin, arcanist, luminary, wildstrider, oathkeeper, necromancer, lorekeeper"
    }
  }
}
```

### Delete Character

| | |
|---|---|
| **Endpoint** | `DELETE /api/v1/characters/<id>` |
| **Description** | Permanently delete character and all associated game sessions |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": {
    "message": "Character deleted successfully",
    "character_id": "char_123"
  }
}
```

### Unlock Skill

| | |
|---|---|
| **Endpoint** | `POST /api/v1/characters/<id>/skills/unlock` |
| **Description** | Unlock skill node (validates prerequisites and skill points) |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "skill_id": "shield_bash"
}
```

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": {
    "message": "Skill unlocked successfully",
    "character_id": "char_123",
    "skill_id": "shield_bash",
    "unlocked_skills": ["shield_bash"],
    "available_points": 0
  }
}
```

**Error Response (400 Bad Request - Prerequisites Not Met):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 400,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": null,
  "error": {
    "code": "SKILL_UNLOCK_ERROR",
    "message": "Prerequisite not met: iron_defense required for fortified_resolve",
    "details": {}
  }
}
```

**Error Response (400 Bad Request - No Skill Points):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 400,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": null,
  "error": {
    "code": "SKILL_UNLOCK_ERROR",
    "message": "No skill points available (Level 1, 1 skills unlocked)",
    "details": {}
  }
}
```

### Respec Skills

| | |
|---|---|
| **Endpoint** | `POST /api/v1/characters/<id>/skills/respec` |
| **Description** | Reset all unlocked skills (costs level × 100 gold) |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": {
    "message": "Skills reset successfully",
    "character_id": "char_123",
    "cost": 500,
    "remaining_gold": 500,
    "available_points": 5
  }
}
```

**Error Response (400 Bad Request - Insufficient Gold):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 400,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": null,
  "error": {
    "code": "INSUFFICIENT_GOLD",
    "message": "Insufficient gold for respec. Cost: 500, Available: 100",
    "details": {}
  }
}
```

---

## Classes & Origins (Reference Data)

### List Classes

| | |
|---|---|
| **Endpoint** | `GET /api/v1/classes` |
| **Description** | Get all available player classes |
| **Auth Required** | No |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": {
    "classes": [
      {
        "class_id": "vanguard",
        "name": "Vanguard",
        "description": "A seasoned warrior who stands at the front lines...",
        "base_stats": {
          "strength": 14,
          "dexterity": 10,
          "constitution": 14,
          "intelligence": 8,
          "wisdom": 10,
          "charisma": 9
        },
        "skill_trees": ["Shield Bearer", "Weapon Master"],
        "starting_equipment": ["rusty_sword"],
        "starting_abilities": ["basic_attack"]
      },
      {
        "class_id": "assassin",
        "name": "Assassin",
        "description": "A master of stealth and precision...",
        "base_stats": {
          "strength": 11,
          "dexterity": 15,
          "constitution": 10,
          "intelligence": 9,
          "wisdom": 10,
          "charisma": 10
        },
        "skill_trees": ["Shadow Dancer", "Blade Specialist"],
        "starting_equipment": ["rusty_dagger"],
        "starting_abilities": ["basic_attack"]
      }
    ],
    "count": 8
  }
}
```

### Get Class

| | |
|---|---|
| **Endpoint** | `GET /api/v1/classes/<class_id>` |
| **Description** | Get full class details with skill trees |
| **Auth Required** | No |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": {
    "class_id": "vanguard",
    "name": "Vanguard",
    "description": "A seasoned warrior who stands at the front lines...",
    "base_stats": {
      "strength": 14,
      "dexterity": 10,
      "constitution": 14,
      "intelligence": 8,
      "wisdom": 10,
      "charisma": 9
    },
    "skill_trees": [
      {
        "tree_id": "shield_bearer",
        "name": "Shield Bearer",
        "description": "Defensive tanking specialization",
        "nodes": [
          {
            "skill_id": "shield_bash",
            "name": "Shield Bash",
            "description": "Strike with shield for 120% damage and 1-turn stun",
            "tier": 1,
            "prerequisites": [],
            "effects": {
              "damage_multiplier": 1.2
            },
            "unlocks_abilities": ["shield_bash"]
          }
        ]
      },
      {
        "tree_id": "weapon_master",
        "name": "Weapon Master",
        "description": "Offensive damage specialization",
        "nodes": []
      }
    ],
    "starting_equipment": ["rusty_sword"],
    "starting_abilities": ["basic_attack"]
  }
}
```

**Error Response (404 Not Found):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 404,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": null,
  "error": {
    "code": "NOT_FOUND",
    "message": "Class not found: invalid_class",
    "details": {}
  }
}
```

### List Origins

| | |
|---|---|
| **Endpoint** | `GET /api/v1/origins` |
| **Description** | Get all available character origins |
| **Auth Required** | No |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-15T12:00:00Z",
  "result": {
    "origins": [
      {
        "id": "soul_revenant",
        "name": "Soul Revenant",
        "description": "Centuries ago, you perished in battle...",
        "starting_location": {
          "id": "forgotten_crypt",
          "name": "The Forgotten Crypt",
          "region": "The Deadlands",
          "description": "An ancient burial site shrouded in mist..."
        },
        "narrative_hooks": [
          "Why were you brought back to life?",
          "What happened in the centuries you were dead?",
          "Do you remember your past life?",
          "Who or what resurrected you?",
          "Are there others like you?",
          "What is your purpose now?"
        ],
        "starting_bonus": {
          "trait": "Deathless Resolve",
          "description": "Having already died once, death holds no fear for you",
          "effect": "Once per day, when reduced to 0 HP, survive with 1 HP instead"
        }
      }
    ],
    "count": 4
  }
}
```

---

## Health

### Health Check

| | |
|---|---|
| **Endpoint** | `GET /api/v1/health` |
| **Description** | Check API service status and version |
| **Auth Required** | No |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-16T10:30:00Z",
  "result": {
    "status": "ok",
    "service": "Code of Conquest API",
    "version": "0.1.0"
  },
  "error": null,
  "meta": {}
}
```

---

## Sessions

### List Sessions

| | |
|---|---|
| **Endpoint** | `GET /api/v1/sessions` |
| **Description** | Get all active sessions for current user |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-16T10:30:00Z",
  "result": [
    {
      "session_id": "sess_789",
      "character_id": "char_456",
      "turn_number": 5,
      "status": "active",
      "created_at": "2025-11-16T10:00:00Z",
      "last_activity": "2025-11-16T10:25:00Z",
      "game_state": {
        "current_location": "crossville_village",
        "location_type": "town"
      }
    }
  ]
}
```

**Error Responses:**
- `401` - Not authenticated
- `500` - Internal server error

---

### Create Session

| | |
|---|---|
| **Endpoint** | `POST /api/v1/sessions` |
| **Description** | Create new solo game session |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "character_id": "char_456"
}
```

**Response (201 Created):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 201,
  "timestamp": "2025-11-16T10:30:00Z",
  "result": {
    "session_id": "sess_789",
    "character_id": "char_456",
    "turn_number": 0,
    "game_state": {
      "current_location": "Crossroads Village",
      "location_type": "town",
      "active_quests": []
    }
  }
}
```

**Error Responses:**
- `400` - Validation error (missing character_id)
- `404` - Character not found
- `409` - Session limit exceeded (tier-based limit)

**Session Limits by Tier:**
| Tier | Max Active Sessions |
|------|---------------------|
| FREE | 1 |
| BASIC | 2 |
| PREMIUM | 3 |
| ELITE | 5 |

**Error Response (409 Conflict - Session Limit Exceeded):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 409,
  "timestamp": "2025-11-16T10:30:00Z",
  "result": null,
  "error": {
    "code": "SESSION_LIMIT_EXCEEDED",
    "message": "Maximum active sessions reached for free tier (1/1). Please delete an existing session to start a new one."
  }
}
```

---

### Get Session State

| | |
|---|---|
| **Endpoint** | `GET /api/v1/sessions/<session_id>` |
| **Description** | Get current session state with available actions |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-16T10:30:00Z",
  "result": {
    "session_id": "sess_789",
    "character_id": "char_456",
    "turn_number": 5,
    "status": "active",
    "game_state": {
      "current_location": "The Rusty Anchor",
      "location_type": "tavern",
      "active_quests": ["quest_goblin_cave"]
    },
    "available_actions": [
      {
        "prompt_id": "ask_locals",
        "display_text": "Ask locals for information",
        "description": "Talk to NPCs to learn about quests and rumors",
        "category": "ask_question"
      },
      {
        "prompt_id": "rest",
        "display_text": "Rest and recover",
        "description": "Take a short rest to recover health",
        "category": "rest"
      }
    ]
  }
}
```

**Error Responses:**
- `404` - Session not found or not owned by user

---

### Take Action

| | |
|---|---|
| **Endpoint** | `POST /api/v1/sessions/<session_id>/action` |
| **Description** | Submit action for AI processing (async) |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "action_type": "button",
  "prompt_id": "ask_locals"
}
```

**Response (202 Accepted):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 202,
  "timestamp": "2025-11-16T10:30:00Z",
  "result": {
    "job_id": "abc-123",
    "status": "queued",
    "message": "Your action is being processed..."
  }
}
```

**Notes:**
- Only button actions with predefined prompts are supported
- Poll `/api/v1/jobs/<job_id>/status` to check processing status
- Rate limits apply based on subscription tier
- Available actions depend on user tier and current location

**Error Responses:**
- `400` - Validation error (invalid action_type, missing prompt_id)
- `403` - Action not available for tier/location
- `404` - Session not found
- `429` - Rate limit exceeded

**Rate Limit Error Response (429):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 429,
  "timestamp": "2025-11-16T10:30:00Z",
  "result": null,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Daily turn limit reached (20 turns). Resets at 00:00 UTC",
    "details": {}
  }
}
```

---

### Get Job Status

| | |
|---|---|
| **Endpoint** | `GET /api/v1/jobs/<job_id>/status` |
| **Description** | Get status of an async AI processing job |
| **Auth Required** | Yes |

**Response (200 OK - Processing):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-16T10:30:05Z",
  "result": {
    "job_id": "ai_TaskType.NARRATIVE_abc123",
    "status": "processing"
  }
}
```

**Response (200 OK - Completed):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-16T10:30:10Z",
  "result": {
    "job_id": "ai_TaskType.NARRATIVE_abc123",
    "status": "completed",
    "dm_response": "As you search for supplies in the village, you notice...",
    "tokens_used": 273,
    "model": "meta/meta-llama-3-8b-instruct"
  }
}
```

**Response (200 OK - Failed):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-16T10:30:10Z",
  "result": {
    "job_id": "ai_TaskType.NARRATIVE_abc123",
    "status": "failed",
    "error": "AI generation failed"
  }
}
```

**Error Responses:**
- `404` - Job not found

---

### Get Conversation History

| | |
|---|---|
| **Endpoint** | `GET /api/v1/sessions/<session_id>/history` |
| **Description** | Get paginated conversation history |
| **Auth Required** | Yes |

**Query Parameters:**
- `limit` (int, default 20, max 100) - Number of entries to return
- `offset` (int, default 0) - Number of entries to skip

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-16T10:30:00Z",
  "result": {
    "total_turns": 5,
    "history": [
      {
        "turn": 1,
        "action": "I explore the tavern",
        "dm_response": "You enter a smoky tavern filled with weary travelers...",
        "timestamp": "2025-11-16T10:30:00Z"
      },
      {
        "turn": 2,
        "action": "Ask locals for information",
        "dm_response": "A grizzled dwarf at the bar tells you about goblin raids...",
        "timestamp": "2025-11-16T10:32:00Z"
      }
    ],
    "pagination": {
      "limit": 20,
      "offset": 0,
      "has_more": false
    }
  }
}
```

**Error Responses:**
- `404` - Session not found

---

### Delete Session

| | |
|---|---|
| **Endpoint** | `DELETE /api/v1/sessions/<session_id>` |
| **Description** | Permanently delete a session and all associated chat messages |
| **Auth Required** | Yes |

**Behavior:**
- Permanently removes the session from the database (hard delete)
- Also deletes all chat messages associated with this session
- Frees up the session slot for the user's tier limit
- Cannot be undone

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-16T10:30:00Z",
  "result": {
    "message": "Session deleted successfully",
    "session_id": "sess_789"
  }
}
```

**Error Responses:**
- `401` - Not authenticated
- `404` - Session not found or not owned by user
- `500` - Internal server error

---

### Export Session

| | |
|---|---|
| **Endpoint** | `GET /api/v1/sessions/<session_id>/export` |
| **Description** | Export session log as Markdown |
| **Auth Required** | Yes |

**Response:**
```markdown
# Session Log: sess_789
**Date:** 2025-11-14
**Character:** Aragorn the Brave

## Turn 1
**Action:** I explore the tavern
**DM:** You enter a smoky tavern...
```

---

## Travel

The Travel API enables location-based world exploration. Locations are defined in YAML files and players can travel to any unlocked (discovered) location.

### Get Available Destinations

| | |
|---|---|
| **Endpoint** | `GET /api/v1/travel/available` |
| **Description** | Get all locations the player can travel to |
| **Auth Required** | Yes |

**Query Parameters:**
- `session_id` (required) - Active session ID

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T10:30:00Z",
  "result": {
    "current_location": "crossville_village",
    "destinations": [
      {
        "location_id": "crossville_tavern",
        "name": "The Rusty Anchor Tavern",
        "location_type": "tavern",
        "region_id": "crossville",
        "description": "A cozy tavern where travelers share tales..."
      },
      {
        "location_id": "crossville_forest",
        "name": "Whispering Woods",
        "location_type": "wilderness",
        "region_id": "crossville",
        "description": "A dense forest on the outskirts of town..."
      }
    ]
  }
}
```

**Error Responses:**
- `400` - Missing session_id parameter
- `404` - Session or character not found
- `500` - Internal server error

### Travel to Location

| | |
|---|---|
| **Endpoint** | `POST /api/v1/travel` |
| **Description** | Travel to a new location (must be discovered) |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "session_id": "sess_789",
  "location_id": "crossville_tavern"
}
```

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T10:30:00Z",
  "result": {
    "location": {
      "location_id": "crossville_tavern",
      "name": "The Rusty Anchor Tavern",
      "location_type": "tavern",
      "region_id": "crossville",
      "description": "A cozy tavern where travelers share tales...",
      "lore": "Founded decades ago by a retired adventurer...",
      "ambient_description": "The scent of ale and roasting meat fills the air...",
      "available_quests": ["quest_missing_trader"],
      "npc_ids": ["npc_grom_ironbeard"],
      "discoverable_locations": ["crossville_forest"],
      "is_starting_location": false,
      "tags": ["tavern", "social", "merchant", "safe"]
    },
    "npcs_present": [
      {
        "npc_id": "npc_grom_ironbeard",
        "name": "Grom Ironbeard",
        "role": "bartender",
        "appearance": "Stout dwarf with a braided grey beard"
      }
    ],
    "game_state": {
      "current_location": "crossville_tavern",
      "location_type": "tavern",
      "active_quests": []
    }
  }
}
```

**Error Responses:**
- `400` - Location not discovered
- `403` - Location not discovered
- `404` - Session or location not found
- `500` - Internal server error

### Get Location Details

| | |
|---|---|
| **Endpoint** | `GET /api/v1/travel/location/<location_id>` |
| **Description** | Get full details for a specific location |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T10:30:00Z",
  "result": {
    "location": {
      "location_id": "crossville_village",
      "name": "Crossville Village",
      "location_type": "town",
      "region_id": "crossville",
      "description": "A modest farming village built around a central square...",
      "lore": "Founded two centuries ago by settlers from the eastern kingdoms...",
      "ambient_description": "The village square bustles with activity...",
      "available_quests": ["quest_mayors_request"],
      "npc_ids": ["npc_mayor_aldric", "npc_blacksmith_hilda"],
      "discoverable_locations": ["crossville_tavern", "crossville_forest"],
      "is_starting_location": true,
      "tags": ["town", "social", "merchant", "safe"]
    },
    "npcs_present": [
      {
        "npc_id": "npc_mayor_aldric",
        "name": "Mayor Aldric",
        "role": "village mayor",
        "appearance": "A portly man in fine robes"
      }
    ]
  }
}
```

**Error Responses:**
- `404` - Location not found
- `500` - Internal server error

### Get Current Location

| | |
|---|---|
| **Endpoint** | `GET /api/v1/travel/current` |
| **Description** | Get current location details with NPCs present |
| **Auth Required** | Yes |

**Query Parameters:**
- `session_id` (required) - Active session ID

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T10:30:00Z",
  "result": {
    "location": {
      "location_id": "crossville_village",
      "name": "Crossville Village",
      "location_type": "town",
      "description": "A modest farming village..."
    },
    "npcs_present": [
      {
        "npc_id": "npc_mayor_aldric",
        "name": "Mayor Aldric",
        "role": "village mayor"
      },
      {
        "npc_id": "npc_blacksmith_hilda",
        "name": "Hilda Ironforge",
        "role": "blacksmith"
      }
    ]
  }
}
```

---

## NPCs

The NPC API enables interaction with persistent NPCs. NPCs have personalities, knowledge, and relationships that affect dialogue generation.

### Get NPC Details

| | |
|---|---|
| **Endpoint** | `GET /api/v1/npcs/<npc_id>` |
| **Description** | Get NPC details with knowledge filtered by relationship |
| **Auth Required** | Yes |

**Query Parameters:**
- `character_id` (optional) - Filter knowledge by character's relationship

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T10:30:00Z",
  "result": {
    "npc_id": "npc_grom_ironbeard",
    "name": "Grom Ironbeard",
    "role": "bartender",
    "location_id": "crossville_tavern",
    "image_url": "/static/images/npcs/crossville/grom_ironbeard.png",
    "personality": {
      "traits": ["gruff", "observant", "secretly kind"],
      "speech_style": "Uses dwarven expressions, speaks in short sentences",
      "quirks": ["Polishes same mug when thinking", "Snorts when amused"]
    },
    "appearance": {
      "brief": "Stout dwarf with a braided grey beard",
      "detailed": "A weathered dwarf with deep-set eyes..."
    },
    "available_knowledge": [
      "The mayor has been acting strange lately",
      "Strange lights seen in the forest"
    ],
    "interaction_summary": {
      "interaction_count": 3,
      "relationship_level": 65,
      "first_met": "2025-11-20T10:30:00Z"
    },
    "tags": ["merchant", "quest_giver", "information"]
  }
}
```

### Talk to NPC

| | |
|---|---|
| **Endpoint** | `POST /api/v1/npcs/<npc_id>/talk` |
| **Description** | Start or continue conversation with NPC (generates AI dialogue) |
| **Auth Required** | Yes |

**Request Body (Initial Greeting):**
```json
{
  "session_id": "sess_789",
  "topic": "greeting"
}
```

**Request Body (Player Response - Bidirectional Dialogue):**
```json
{
  "session_id": "sess_789",
  "player_response": "What can you tell me about the bandits?"
}
```

**Parameters:**
- `session_id` (required): Active game session ID
- `topic` (optional): Conversation topic for initial greeting (default: "greeting")
- `player_response` (optional): Player's custom message to the NPC. If provided, overrides `topic`. Enables bidirectional conversation.

**Response (202 Accepted):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 202,
  "timestamp": "2025-11-25T10:30:00Z",
  "result": {
    "job_id": "ai_npc_dialogue_xyz789",
    "status": "queued",
    "message": "Starting conversation with Grom Ironbeard...",
    "npc_name": "Grom Ironbeard",
    "npc_role": "bartender"
  }
}
```

**Job Result (when completed):**
```json
{
  "job_id": "ai_npc_dialogue_xyz789",
  "status": "completed",
  "result": {
    "context_type": "npc_dialogue",
    "dialogue": "*polishes mug thoughtfully* \"Ah, another adventurer. What'll it be?\"",
    "tokens_used": 728,
    "npc_name": "Grom Ironbeard",
    "npc_id": "npc_grom_ironbeard",
    "character_name": "Thorin",
    "player_line": "greeting",
    "conversation_history": [
      {
        "player_line": "Hello there!",
        "npc_response": "*nods gruffly* \"Welcome to the Rusty Anchor.\""
      },
      {
        "player_line": "What's the news around town?",
        "npc_response": "*leans in* \"Strange folk been coming through lately...\""
      }
    ]
  }
}
```

**Conversation History:**
- Previous dialogue exchanges are automatically stored per character-NPC pair
- Up to 10 exchanges are kept per NPC (oldest are pruned)
- The AI receives the last 3 exchanges as context for continuity
- The job result includes prior `conversation_history` for UI display

**Bidirectional Dialogue:**
- If `player_response` is provided in the request, it overrides `topic` and enables full bidirectional conversation
- The player's response is stored in the conversation history
- The NPC's reply takes into account the full conversation context

**Error Responses:**
- `400` - NPC not at current location
- `404` - NPC or session not found

### Get NPCs at Location

| | |
|---|---|
| **Endpoint** | `GET /api/v1/npcs/at-location/<location_id>` |
| **Description** | Get all NPCs present at a location |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T10:30:00Z",
  "result": {
    "location_id": "crossville_tavern",
    "npcs": [
      {
        "npc_id": "npc_grom_ironbeard",
        "name": "Grom Ironbeard",
        "role": "bartender",
        "appearance": "Stout dwarf with a braided grey beard",
        "tags": ["merchant", "quest_giver"],
        "image_url": "/static/images/npcs/crossville/grom_ironbeard.png"
      },
      {
        "npc_id": "npc_mira_swiftfoot",
        "name": "Mira Swiftfoot",
        "role": "traveling rogue",
        "appearance": "Lithe half-elf with sharp eyes",
        "tags": ["information", "secret_keeper"],
        "image_url": "/static/images/npcs/crossville/mira_swiftfoot.png"
      }
    ]
  }
}
```

### Adjust NPC Relationship

| | |
|---|---|
| **Endpoint** | `POST /api/v1/npcs/<npc_id>/relationship` |
| **Description** | Adjust relationship level with NPC |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "character_id": "char_123",
  "adjustment": 10
}
```

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T10:30:00Z",
  "result": {
    "npc_id": "npc_grom_ironbeard",
    "relationship_level": 75
  }
}
```

### Set NPC Custom Flag

| | |
|---|---|
| **Endpoint** | `POST /api/v1/npcs/<npc_id>/flag` |
| **Description** | Set custom flag on NPC interaction (for conditional secrets) |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "character_id": "char_123",
  "flag_name": "helped_with_rats",
  "flag_value": true
}
```

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T10:30:00Z",
  "result": {
    "npc_id": "npc_grom_ironbeard",
    "flag_name": "helped_with_rats",
    "flag_value": true
  }
}
```

---

## Chat / Conversation History

The Chat API provides access to complete player-NPC conversation history. All dialogue exchanges are stored in the `chat_messages` collection for unlimited history, with the most recent 3 messages cached in character documents for quick AI context.

### Get All Conversations Summary

| | |
|---|---|
| **Endpoint** | `GET /api/v1/characters/<character_id>/chats` |
| **Description** | Get summary of all NPC conversations for a character |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T14:30:00Z",
  "result": {
    "conversations": [
      {
        "npc_id": "npc_grom_ironbeard",
        "npc_name": "Grom Ironbeard",
        "last_message_timestamp": "2025-11-25T14:30:00Z",
        "message_count": 15,
        "recent_preview": "Aye, the rats in the cellar have been causing trouble..."
      },
      {
        "npc_id": "npc_mira_swiftfoot",
        "npc_name": "Mira Swiftfoot",
        "last_message_timestamp": "2025-11-25T12:15:00Z",
        "message_count": 8,
        "recent_preview": "*leans in and whispers* I've heard rumors about the mayor..."
      }
    ]
  }
}
```

### Get Conversation with Specific NPC

| | |
|---|---|
| **Endpoint** | `GET /api/v1/characters/<character_id>/chats/<npc_id>` |
| **Description** | Get paginated conversation history with a specific NPC |
| **Auth Required** | Yes |

**Query Parameters:**
- `limit` (optional): Maximum messages to return (default: 50, max: 100)
- `offset` (optional): Number of messages to skip (default: 0)

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T14:30:00Z",
  "result": {
    "npc_id": "npc_grom_ironbeard",
    "npc_name": "Grom Ironbeard",
    "total_messages": 15,
    "messages": [
      {
        "message_id": "msg_abc123",
        "character_id": "char_123",
        "npc_id": "npc_grom_ironbeard",
        "player_message": "What rumors have you heard?",
        "npc_response": "*leans in* Strange folk been coming through lately...",
        "timestamp": "2025-11-25T14:30:00Z",
        "context": "dialogue",
        "location_id": "crossville_tavern",
        "session_id": "sess_789",
        "metadata": {},
        "is_deleted": false
      },
      {
        "message_id": "msg_abc122",
        "character_id": "char_123",
        "npc_id": "npc_grom_ironbeard",
        "player_message": "Hello there!",
        "npc_response": "*nods gruffly* Welcome to the Rusty Anchor.",
        "timestamp": "2025-11-25T14:25:00Z",
        "context": "dialogue",
        "location_id": "crossville_tavern",
        "session_id": "sess_789",
        "metadata": {},
        "is_deleted": false
      }
    ],
    "pagination": {
      "limit": 50,
      "offset": 0,
      "has_more": false
    }
  }
}
```

**Message Context Types:**
- `dialogue` - General conversation
- `quest_offered` - Quest offering dialogue
- `quest_completed` - Quest completion dialogue
- `shop` - Merchant transaction
- `location_revealed` - New location discovered
- `lore` - Lore/backstory reveals

### Search Messages

| | |
|---|---|
| **Endpoint** | `GET /api/v1/characters/<character_id>/chats/search` |
| **Description** | Search messages by text with optional filters |
| **Auth Required** | Yes |

**Query Parameters:**
- `q` (required): Search text to find in player_message and npc_response
- `npc_id` (optional): Filter by specific NPC
- `context` (optional): Filter by message context type
- `date_from` (optional): Start date in ISO format (e.g., 2025-11-25T00:00:00Z)
- `date_to` (optional): End date in ISO format
- `limit` (optional): Maximum messages to return (default: 50, max: 100)
- `offset` (optional): Number of messages to skip (default: 0)

**Example Request:**
```
GET /api/v1/characters/char_123/chats/search?q=quest&npc_id=npc_grom_ironbeard&context=quest_offered
```

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T14:30:00Z",
  "result": {
    "search_text": "quest",
    "filters": {
      "npc_id": "npc_grom_ironbeard",
      "context": "quest_offered",
      "date_from": null,
      "date_to": null
    },
    "total_results": 2,
    "messages": [
      {
        "message_id": "msg_abc125",
        "character_id": "char_123",
        "npc_id": "npc_grom_ironbeard",
        "player_message": "Do you have any work for me?",
        "npc_response": "*sighs heavily* Aye, there's rats in me cellar. Big ones.",
        "timestamp": "2025-11-25T13:00:00Z",
        "context": "quest_offered",
        "location_id": "crossville_tavern",
        "session_id": "sess_789",
        "metadata": {
          "quest_id": "quest_cellar_rats"
        },
        "is_deleted": false
      }
    ],
    "pagination": {
      "limit": 50,
      "offset": 0,
      "has_more": false
    }
  }
}
```

### Delete Message (Soft Delete)

| | |
|---|---|
| **Endpoint** | `DELETE /api/v1/characters/<character_id>/chats/<message_id>` |
| **Description** | Soft delete a message (privacy/moderation) |
| **Auth Required** | Yes |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-25T14:30:00Z",
  "result": {
    "message_id": "msg_abc123",
    "deleted": true
  }
}
```

**Notes:**
- Messages are soft deleted (is_deleted=true), not removed from database
- Deleted messages are filtered from all queries
- Only the character owner can delete their own messages

**Error Responses:**
- `403` - User does not own the character
- `404` - Message not found

---

## Combat

### Attack

| | |
|---|---|
| **Endpoint** | `POST /api/v1/combat/<session_id>/attack` |
| **Description** | Execute physical attack |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "attacker_id": "char123",
  "target_id": "enemy1",
  "weapon_id": "sword1"
}
```

**Response:**
```json
{
  "result": {
    "damage": 15,
    "critical": true,
    "narrative": "Aragorn's blade strikes true...",
    "target_hp": 25
  }
}
```

### Cast Spell

| | |
|---|---|
| **Endpoint** | `POST /api/v1/combat/<session_id>/cast` |
| **Description** | Cast spell or ability |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "caster_id": "char123",
  "spell_id": "fireball",
  "target_id": "enemy1"
}
```

**Response:**
```json
{
  "result": {
    "damage": 30,
    "mana_cost": 15,
    "narrative": "Flames engulf the target...",
    "effects_applied": ["burning"]
  }
}
```

### Use Item

| | |
|---|---|
| **Endpoint** | `POST /api/v1/combat/<session_id>/item` |
| **Description** | Use item from inventory |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "character_id": "char123",
  "item_id": "health_potion",
  "target_id": "char123"
}
```

**Response:**
```json
{
  "result": {
    "healing": 50,
    "narrative": "You drink the potion and feel refreshed",
    "current_hp": 100
  }
}
```

### Defend

| | |
|---|---|
| **Endpoint** | `POST /api/v1/combat/<session_id>/defend` |
| **Description** | Take defensive stance |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "character_id": "char123"
}
```

**Response:**
```json
{
  "result": {
    "defense_bonus": 10,
    "duration": 1,
    "narrative": "You brace for impact"
  }
}
```

### Get Combat Status

| | |
|---|---|
| **Endpoint** | `GET /api/v1/combat/<session_id>/status` |
| **Description** | Get current combat state |
| **Auth Required** | Yes |

**Response:**
```json
{
  "result": {
    "combatants": [],
    "turn_order": [],
    "current_turn": 0,
    "round_number": 1,
    "status": "active"
  }
}
```

---

## Game Mechanics

### Perform Skill Check or Search

| | |
|---|---|
| **Endpoint** | `POST /api/v1/game/check` |
| **Description** | Perform a skill check or search action and get deterministic dice roll results |
| **Auth Required** | Yes |

**Request Body (Skill Check):**
```json
{
  "character_id": "char_123",
  "check_type": "skill",
  "skill": "persuasion",
  "dc": 15,
  "bonus": 2,
  "context": {
    "npc_name": "Guard Captain"
  }
}
```

**Request Body (Search Action):**
```json
{
  "character_id": "char_123",
  "check_type": "search",
  "location_type": "forest",
  "dc": 12,
  "bonus": 0
}
```

**Request Body (Using Difficulty Name):**
```json
{
  "character_id": "char_123",
  "check_type": "skill",
  "skill": "stealth",
  "difficulty": "hard",
  "bonus": 1
}
```

**Response (200 OK - Skill Check Success):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-23T10:30:00Z",
  "result": {
    "check_result": {
      "roll": 16,
      "modifier": 3,
      "total": 19,
      "dc": 15,
      "success": true,
      "margin": 4,
      "skill_type": "persuasion"
    },
    "context": {
      "skill_used": "persuasion",
      "stat_used": "charisma",
      "npc_name": "Guard Captain"
    }
  }
}
```

**Response (200 OK - Search Success):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-23T10:30:00Z",
  "result": {
    "check_result": {
      "roll": 18,
      "modifier": 2,
      "total": 20,
      "dc": 12,
      "success": true,
      "margin": 8,
      "skill_type": "perception"
    },
    "items_found": [
      {
        "template_key": "ancient_coin",
        "name": "Ancient Coin",
        "description": "A weathered coin from ages past",
        "value": 25
      },
      {
        "template_key": "healing_herb",
        "name": "Healing Herb",
        "description": "A medicinal plant bundle",
        "value": 10
      }
    ],
    "gold_found": 15
  }
}
```

**Response (200 OK - Check Failed):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-23T10:30:00Z",
  "result": {
    "check_result": {
      "roll": 7,
      "modifier": 1,
      "total": 8,
      "dc": 15,
      "success": false,
      "margin": -7,
      "skill_type": "stealth"
    },
    "context": {
      "skill_used": "stealth",
      "stat_used": "dexterity",
      "situation": "Sneaking past guards"
    }
  }
}
```

**Error Response (400 Bad Request - Invalid skill):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 400,
  "timestamp": "2025-11-23T10:30:00Z",
  "result": null,
  "error": {
    "code": "INVALID_INPUT",
    "message": "Invalid skill type",
    "details": {
      "field": "skill",
      "issue": "Must be one of: perception, insight, survival, medicine, stealth, acrobatics, sleight_of_hand, lockpicking, persuasion, deception, intimidation, performance, athletics, arcana, history, investigation, nature, religion, endurance"
    }
  }
}
```

**Error Response (404 Not Found - Character not found):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 404,
  "timestamp": "2025-11-23T10:30:00Z",
  "result": null,
  "error": {
    "code": "NOT_FOUND",
    "message": "Character not found: char_999"
  }
}
```

**Error Response (403 Forbidden - Not character owner):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 403,
  "timestamp": "2025-11-23T10:30:00Z",
  "result": null,
  "error": {
    "code": "FORBIDDEN",
    "message": "You don't have permission to access this character"
  }
}
```

**Notes:**
- `check_type` must be "search" or "skill"
- For skill checks, `skill` is required
- For search checks, `location_type` is optional (defaults to "default")
- `dc` or `difficulty` must be provided (dc takes precedence)
- Valid difficulty values: trivial (5), easy (10), medium (15), hard (20), very_hard (25), nearly_impossible (30)
- `bonus` is optional (defaults to 0)
- `context` is optional and merged with the response for AI narration
- Roll uses d20 + stat modifier + optional bonus
- Margin is calculated as (total - dc)
- Items found depend on location type and success margin

---

### List Available Skills

| | |
|---|---|
| **Endpoint** | `GET /api/v1/game/skills` |
| **Description** | Get all available skill types with their associated stats |
| **Auth Required** | No |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-23T10:30:00Z",
  "result": {
    "skills": [
      {
        "name": "perception",
        "stat": "wisdom"
      },
      {
        "name": "insight",
        "stat": "wisdom"
      },
      {
        "name": "survival",
        "stat": "wisdom"
      },
      {
        "name": "medicine",
        "stat": "wisdom"
      },
      {
        "name": "stealth",
        "stat": "dexterity"
      },
      {
        "name": "acrobatics",
        "stat": "dexterity"
      },
      {
        "name": "sleight_of_hand",
        "stat": "dexterity"
      },
      {
        "name": "lockpicking",
        "stat": "dexterity"
      },
      {
        "name": "persuasion",
        "stat": "charisma"
      },
      {
        "name": "deception",
        "stat": "charisma"
      },
      {
        "name": "intimidation",
        "stat": "charisma"
      },
      {
        "name": "performance",
        "stat": "charisma"
      },
      {
        "name": "athletics",
        "stat": "strength"
      },
      {
        "name": "arcana",
        "stat": "intelligence"
      },
      {
        "name": "history",
        "stat": "intelligence"
      },
      {
        "name": "investigation",
        "stat": "intelligence"
      },
      {
        "name": "nature",
        "stat": "intelligence"
      },
      {
        "name": "religion",
        "stat": "intelligence"
      },
      {
        "name": "endurance",
        "stat": "constitution"
      }
    ]
  }
}
```

**Notes:**
- No authentication required
- Skills are grouped by their associated stat
- Use the skill names in the `skill` parameter of the `/check` endpoint

---

### List Difficulty Levels

| | |
|---|---|
| **Endpoint** | `GET /api/v1/game/difficulties` |
| **Description** | Get all difficulty levels and their DC values |
| **Auth Required** | No |

**Response (200 OK):**
```json
{
  "app": "Code of Conquest",
  "version": "0.1.0",
  "status": 200,
  "timestamp": "2025-11-23T10:30:00Z",
  "result": {
    "difficulties": [
      {
        "name": "trivial",
        "dc": 5
      },
      {
        "name": "easy",
        "dc": 10
      },
      {
        "name": "medium",
        "dc": 15
      },
      {
        "name": "hard",
        "dc": 20
      },
      {
        "name": "very_hard",
        "dc": 25
      },
      {
        "name": "nearly_impossible",
        "dc": 30
      }
    ]
  }
}
```

**Notes:**
- No authentication required
- Use difficulty names in the `difficulty` parameter of the `/check` endpoint instead of providing raw DC values
- DC values range from 5 (trivial) to 30 (nearly impossible)

---

## Marketplace

### Browse Listings

| | |
|---|---|
| **Endpoint** | `GET /api/v1/marketplace` |
| **Description** | Browse marketplace listings |
| **Auth Required** | Yes (Premium+ only) |

**Query Parameters:**
- `type` - "auction" or "fixed_price"
- `category` - "weapon", "armor", "consumable"
- `min_price` - Minimum price
- `max_price` - Maximum price
- `sort` - "price_asc", "price_desc", "ending_soon"
- `page` - Page number
- `limit` - Items per page

**Response:**
```json
{
  "result": {
    "listings": [
      {
        "listing_id": "list123",
        "item": {},
        "listing_type": "auction",
        "current_bid": 500,
        "buyout_price": 1000,
        "auction_end": "2025-11-15T12:00:00Z"
      }
    ],
    "total": 50,
    "page": 1,
    "pages": 5
  }
}
```

### Get Listing

| | |
|---|---|
| **Endpoint** | `GET /api/v1/marketplace/<id>` |
| **Description** | Get listing details |
| **Auth Required** | Yes (Premium+ only) |

**Response:**
```json
{
  "result": {
    "listing_id": "list123",
    "seller_name": "Aragorn",
    "item": {},
    "listing_type": "auction",
    "current_bid": 500,
    "bid_count": 5,
    "bids": [],
    "auction_end": "2025-11-15T12:00:00Z"
  }
}
```

### Create Listing

| | |
|---|---|
| **Endpoint** | `POST /api/v1/marketplace/list` |
| **Description** | Create new marketplace listing |
| **Auth Required** | Yes (Premium+ only) |

**Request Body (Auction):**
```json
{
  "character_id": "char123",
  "item_id": "sword1",
  "listing_type": "auction",
  "starting_bid": 100,
  "buyout_price": 1000,
  "duration_hours": 48
}
```

**Request Body (Fixed Price):**
```json
{
  "character_id": "char123",
  "item_id": "sword1",
  "listing_type": "fixed_price",
  "price": 500
}
```

**Response:**
```json
{
  "result": {
    "listing_id": "list123",
    "message": "Listing created successfully"
  }
}
```

### Place Bid

| | |
|---|---|
| **Endpoint** | `POST /api/v1/marketplace/<id>/bid` |
| **Description** | Place bid on auction |
| **Auth Required** | Yes (Premium+ only) |

**Request Body:**
```json
{
  "character_id": "char123",
  "amount": 600
}
```

**Response:**
```json
{
  "result": {
    "current_bid": 600,
    "is_winning": true,
    "message": "Bid placed successfully"
  }
}
```

### Buyout

| | |
|---|---|
| **Endpoint** | `POST /api/v1/marketplace/<id>/buyout` |
| **Description** | Instant purchase at buyout price |
| **Auth Required** | Yes (Premium+ only) |

**Request Body:**
```json
{
  "character_id": "char123"
}
```

**Response:**
```json
{
  "result": {
    "transaction_id": "trans123",
    "price": 1000,
    "item": {},
    "message": "Purchase successful"
  }
}
```

### Cancel Listing

| | |
|---|---|
| **Endpoint** | `DELETE /api/v1/marketplace/<id>` |
| **Description** | Cancel listing (owner only) |
| **Auth Required** | Yes (Premium+ only) |

**Response:**
```json
{
  "result": {
    "message": "Listing cancelled, item returned"
  }
}
```

### My Listings

| | |
|---|---|
| **Endpoint** | `GET /api/v1/marketplace/my-listings` |
| **Description** | Get current user's active listings |
| **Auth Required** | Yes (Premium+ only) |

**Response:**
```json
{
  "result": {
    "listings": [],
    "total": 5
  }
}
```

### My Bids

| | |
|---|---|
| **Endpoint** | `GET /api/v1/marketplace/my-bids` |
| **Description** | Get current user's active bids |
| **Auth Required** | Yes (Premium+ only) |

**Response:**
```json
{
  "result": {
    "bids": [
      {
        "listing_id": "list123",
        "item": {},
        "your_bid": 500,
        "current_bid": 600,
        "is_winning": false,
        "auction_end": "2025-11-15T12:00:00Z"
      }
    ]
  }
}
```

---

## Shop

### Browse Shop

| | |
|---|---|
| **Endpoint** | `GET /api/v1/shop/items` |
| **Description** | Browse NPC shop inventory |
| **Auth Required** | Yes |

**Query Parameters:**
- `category` - "consumable", "weapon", "armor"

**Response:**
```json
{
  "result": {
    "items": [
      {
        "item_id": "health_potion",
        "name": "Health Potion",
        "price": 50,
        "stock": -1,
        "description": "Restores 50 HP"
      }
    ]
  }
}
```

### Purchase from Shop

| | |
|---|---|
| **Endpoint** | `POST /api/v1/shop/purchase` |
| **Description** | Buy item from NPC shop |
| **Auth Required** | Yes |

**Request Body:**
```json
{
  "character_id": "char123",
  "item_id": "health_potion",
  "quantity": 5
}
```

**Response:**
```json
{
  "result": {
    "transaction_id": "trans123",
    "total_cost": 250,
    "items_purchased": 5,
    "remaining_gold": 750
  }
}
```

---

## Error Responses

### Standard Error Format

```json
{
  "app": "AI Dungeon Master",
  "version": "1.0.0",
  "status": 400,
  "timestamp": "2025-11-14T12:00:00Z",
  "result": null,
  "error": {
    "code": "INVALID_ACTION",
    "message": "Not your turn",
    "details": {
      "current_turn": "char456",
      "your_character": "char123"
    }
  }
}
```

### Common Error Codes

| Code | Status | Description |
|------|--------|-------------|
| `UNAUTHORIZED` | 401 | Invalid or missing auth token |
| `FORBIDDEN` | 403 | Insufficient permissions |
| `NOT_FOUND` | 404 | Resource not found |
| `INVALID_INPUT` | 400 | Validation error |
| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests |
| `CHARACTER_LIMIT_EXCEEDED` | 400 | User has reached character limit for their tier |
| `SESSION_LIMIT_EXCEEDED` | 409 | User has reached session limit for their tier |
| `CHARACTER_NOT_FOUND` | 404 | Character does not exist or not accessible |
| `SKILL_UNLOCK_ERROR` | 400 | Skill unlock failed (prerequisites, points, or tier) |
| `INSUFFICIENT_FUNDS` | 400 | Not enough gold |
| `INVALID_ACTION` | 400 | Action not allowed |
| `SESSION_FULL` | 400 | Session at max capacity |
| `NOT_YOUR_TURN` | 400 | Not active player's turn |
| `AI_LIMIT_EXCEEDED` | 429 | Daily AI call limit reached |
| `PREMIUM_REQUIRED` | 403 | Feature requires premium subscription |

---

## Rate Limiting

| Tier | Requests/Minute | AI Calls/Day | Max Sessions |
|------|-----------------|--------------|--------------|
| FREE | 30 | 50 | 1 |
| BASIC | 60 | 200 | 2 |
| PREMIUM | 120 | 1000 | 3 |
| ELITE | 300 | Unlimited | 5 |

**Rate Limit Headers:**
```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1699999999
```

---

## Pagination

Endpoints that return lists support pagination:

**Query Parameters:**
- `page` - Page number (default: 1)
- `limit` - Items per page (default: 20, max: 100)

**Response Meta:**
```json
{
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "pages": 5
  }
}
```

---

## Realtime Events (WebSocket)

**Subscribe to session updates:**

```javascript
client.subscribe(
  'databases.main.collections.game_sessions.documents.{sessionId}',
  callback
);
```

**Event Types:**
- Session state change
- Turn change
- Combat update
- Chat message
- Player joined/left
- Marketplace bid notification
