Backend Changes:
- Add tier-based max_sessions config (free: 1, basic: 2, premium: 3, elite: 5)
- Add DELETE /api/v1/sessions/{id} endpoint for hard session deletion
- Cascade delete chat messages when session is deleted
- Add GET /api/v1/usage endpoint for daily turn limit info
- Replace hardcoded TIER_LIMITS with config-based ai_calls_per_day
- Handle unlimited (-1) tier in rate limiter service
Frontend Changes:
- Add inline session delete buttons with HTMX on character list
- Add usage_display.html component showing remaining daily turns
- Display usage indicator on character list and game play pages
- Page refresh after session deletion to update UI state
Documentation:
- Update API_REFERENCE.md with new endpoints and tier limits
- Update API_TESTING.md with session endpoint examples
- Update SESSION_MANAGEMENT.md with tier-based limits
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2700 lines
57 KiB
Markdown
2700 lines
57 KiB
Markdown
# 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** | Delete character (soft delete - marks as inactive) |
|
||
| **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
|