> For the complete documentation index, see [llms.txt](https://docs.idosgames.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.idosgames.com/api/api-v2/character.md).

# Character

### **Overview**

Base URL: `https://api.idosgames.com`

HTTP Method: `POST`

Route: `/api/v2/{TitleTemplateId}/{TitleId}/Client/Character/{Action}/{UserID}`

Documentation: [**LiveOps Configuration & Logic**](https://docs.idosgames.com/liveops/character)

**Actions (CharacterAction):**

* `GetCharacterDefinitions`
* `GetUserCharacters`
* `UpgradeStatLevel`
* `UpgradeCharacterLevel`
* `EquipItems`
* `UnequipItems`
* `UnequipAllCharacters`

### Authentication

#### Required Headers

* `Authorization: Bearer <SessionTicket>`
* `Content-Type: application/json`

#### Unauthorized (401)

Returned when:

* `Authorization` header is missing
* Bearer token is missing/invalid
* Session validation failed

### Response Envelope (Server Standard)

All responses are wrapped with `OperationResult<T>`:

#### Success

```json
{
  "Success": true,
  "Error": null,
  "Data": { }
}
```

#### Failure

```json
{
  "Success": false,
  "Error": "Some error message",
  "Data": null
}
```

#### SuccessResponse

Used by operations like Equip/Unequip:

```json
{
  "IsCompleted": true,
  "ServerTime": "2026-02-09T12:34:56.789Z"
}
```

***

### Quick Reference (Action Table)

|                                                   Action | Request Body Fields               | Response Data Type                                                                                                |
| -------------------------------------------------------: | --------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| [GetCharacterDefinitions](#id-1-getcharacterdefinitions) | *(none)*                          | [`CharacterDefinitions`](#response-operationresult-less-than-characterdefinitions-greater-than)                   |
|             [GetUserCharacters](#id-2-getusercharacters) | *(none)*                          | [`GetCharactersResponse`](#response-operationresult-less-than-getcharactersresponse-greater-than)                 |
|               [UpgradeStatLevel](#id-3-upgradestatlevel) | `CharacterID`, `StatID`           | [`UpgradeStatLevelResponse`](#response-operationresult-less-than-upgradestatlevelresponse-greater-than)           |
|     [UpgradeCharacterLevel](#id-4-upgradecharacterlevel) | `CharacterID`                     | [`UpgradeCharacterLevelResponse`](#response-operationresult-less-than-upgradecharacterlevelresponse-greater-than) |
|                           [EquipItems](#id-5-equipitems) | `CharacterID`, `ItemsToEquip[]`   | [`SuccessResponse`](#response-operationresult-less-than-successresponse-greater-than)                             |
|                       [UnequipItems](#id-6-unequipitems) | `CharacterID`, `UnequipSlotIDs[]` | [`SuccessResponse`](#response-operationresult-less-than-successresponse-greater-than-1)                           |
|       [UnequipAllCharacters](#id-7-unequipallcharacters) | *(none)*                          | [`SuccessResponse`](#response-operationresult-less-than-successresponse-greater-than-2)                           |

> Request body can be `{}` for actions without arguments.

***

## Endpoints

### 1) GetCharacterDefinitions

Returns `CharacterDefinitions`.

#### Request

**Route** `POST /api/v2/{TitleTemplateId}/{TitleId}/Client/Character/GetCharacterDefinitions/{UserID}`

**Body**

```json
{}
```

#### Response (OperationResult\<CharacterDefinitions>)

```json
{
  "Success": true,
  "Error": null,
  "Data": {
    "AllowedCharacterIDs": ["main", "hero_1"],
    "AllowedEquipmentSlotIDs": ["Weapon", "Helmet"],

    "StatDefinitions": [
      {
        "StatID": "Power",
        "MaxLevel": 100,
        "Weight": 10,
        "BaseCostResource": {
          "Type": "VirtualCurrency",
          "CurrencyID": "CO",
          "Amount": 1
        },
        "BaseCost": 50,
        "CostScalingFactor": 1.08,
        "BaseStatValue": 10,
        "StatScalingFactor": 1.05,
        "Requirements": [
          { "RequiredStatID": "HP", "RequiredLevel": 5 }
        ],
        "DisplayName": "Power",
        "IconPath": "icons/power.png",
        "Description": "Increases damage."
      }
    ],

    "CustomStatDefinitions": {
      "hero_1": [
        {
          "StatID": "Power",
          "MaxLevel": 150,
          "BaseCost": 75
        }
      ]
    },

    "LevelDefinitions": [
      {
        "Level": 1,
        "UpgradeCost": [],
        "GlobalStatMultiplier": 1.0,
        "StatMaxLevelMultiplier": 1.0
      },
      {
        "Level": 2,
        "UpgradeCost": [
          { "Type": "Item", "ItemID": "hero_1_shard", "Amount": 10 }
        ],
        "GlobalStatMultiplier": 1.1,
        "StatMaxLevelMultiplier": 1.25
      }
    ],

    "CustomLevelDefinitions": {
      "hero_1": [
        {
          "Level": 2,
          "UpgradeCost": [
            { "Type": "VirtualCurrency", "CurrencyID": "CO", "Amount": 200 }
          ],
          "GlobalStatMultiplier": 1.15,
          "StatMaxLevelMultiplier": 1.5
        }
      ]
    }
  }
}
```

#### Common Errors

```json
{ "Success": false, "Error": "Character definitions not found.", "Data": null }
```

***

### 2) GetUserCharacters

Returns player characters dictionary.

#### Request

**Route** `POST /api/v2/{TitleTemplateId}/{TitleId}/Client/Character/GetUserCharacters/{UserID}`

**Body**

```json
{}
```

#### Response (OperationResult\<GetCharactersResponse>)

```json
{
  "Success": true,
  "Error": null,
  "Data": {
    "Characters": {
      "Main": {
        "CharacterID": "main",
        "Class": "Warrior",
        "Name": "Main Hero",
        "Level": 1,
        "Experience": 0,
        "Power": 120,
        "StatLevels": { "Power": 10, "HP": 5 },
        "Equipment": {
          "Weapon": {
            "SlotID": "Weapon",
            "CatalogVersion": "Items",
            "ItemID": "sword_01",
            "ItemInstanceID": "I_001",
            "EquippedAt": "2026-02-09T12:30:00Z"
          }
        },
        "UpdatedAt": "2026-02-09T12:34:56.789Z"
      }
    }
  }
}
```

#### Empty State

```json
{
  "Success": true,
  "Error": null,
  "Data": { "Characters": {} }
}
```

***

### 3) UpgradeStatLevel

Upgrades a stat by +1 and consumes required resource.

#### Request

**Route** `POST /api/v2/{TitleTemplateId}/{TitleId}/Client/Character/UpgradeStatLevel/{UserID}`

**Body**

```json
{
  "CharacterID": "main",
  "StatID": "Power"
}
```

#### Response (OperationResult\<UpgradeStatLevelResponse>)

```json
{
  "Success": true,
  "Error": null,
  "Data": {
    "StatID": "Power",
    "StatLevel": 11,
    "ConsumedResource": {
      "Type": "VirtualCurrency",
      "CurrencyID": "CO",
      "Amount": 120
    },
    "ConsumedResourceBalance": 880
  }
}
```

#### Common Errors

**Missing StatID**

```json
{ "Success": false, "Error": "StatId is required", "Data": null }
```

**Stat Not Found**

```json
{ "Success": false, "Error": "Stat 'Power' not found in configuration for character 'main'.", "Data": null }
```

**Character Locked**

```json
{ "Success": false, "Error": "Character 'hero_1' is locked. Unlock it first.", "Data": null }
```

**Requirements Not Met**

```json
{ "Success": false, "Error": "The required stat did not reach the desired level.", "Data": null }
```

**Max Reached**

```json
{
  "Success": false,
  "Error": "Already at maximum level (100/100). Upgrade Character Rank to increase limit.",
  "Data": null
}
```

**Fail / Not Enough Resource**

```json
{
  "Success": false,
  "Error": "Upgrade failed (not enough resource / race condition / requirements).",
  "Data": null
}
```

***

### 4) UpgradeCharacterLevel

Upgrades character rank/level by +1 (including unlock 0 → 1).

#### Request

**Route** `POST /api/v2/{TitleTemplateId}/{TitleId}/Client/Character/UpgradeCharacterLevel/{UserID}`

**Body**

```json
{
  "CharacterID": "hero_1"
}
```

#### Response (OperationResult\<UpgradeCharacterLevelResponse>)

```json
{
  "Success": true,
  "Error": null,
  "Data": {
    "CharacterID": "hero_1",
    "NewLevel": 1,
    "ConsumedResources": [
      { "Type": "Item", "ItemID": "hero_1_shard", "Amount": 10 }
    ]
  }
}
```

#### Common Errors

**Not Allowed**

```json
{ "Success": false, "Error": "Character 'hero_1' is not allowed.", "Data": null }
```

**Next Level Config Missing**

```json
{ "Success": false, "Error": "Max level reached or config for level 3 is missing.", "Data": null }
```

**Not Enough Currency**

```json
{ "Success": false, "Error": "Not enough currency: CO", "Data": null }
```

**Not Enough Items**

```json
{ "Success": false, "Error": "Not enough items: hero_1_shard", "Data": null }
```

**Resource Revocation Failed**

```json
{ "Success": false, "Error": "Resource revocation failed.", "Data": null }
```

**Database Error (Refunded)**

```json
{ "Success": false, "Error": "Database error. Resources refunded.", "Data": null }
```

***

### 5) EquipItems

Equips items to specified slots.

#### Request

**Route** `POST /api/v2/{TitleTemplateId}/{TitleId}/Client/Character/EquipItems/{UserID}`

**Body**

```json
{
  "CharacterID": "main",
  "ItemsToEquip": [
    { "SlotID": "Weapon", "ItemInstanceID": "I_001" },
    { "SlotID": "Helmet", "ItemInstanceID": "I_777" }
  ]
}
```

#### Response (OperationResult\<SuccessResponse>)

```json
{
  "Success": true,
  "Error": null,
  "Data": {
    "IsCompleted": true,
    "ServerTime": "2026-02-09T12:34:56.789Z"
  }
}
```

***

### 6) UnequipItems

Unequips items from specified slots.

#### Request

**Route** `POST /api/v2/{TitleTemplateId}/{TitleId}/Client/Character/UnequipItems/{UserID}`

**Body**

```json
{
  "CharacterID": "Main",
  "UnequipSlotIDs": ["Weapon", "Helmet"]
}
```

#### Response (OperationResult\<SuccessResponse>)

```json
{
  "Success": true,
  "Error": null,
  "Data": {
    "IsCompleted": true,
    "ServerTime": "2026-02-09T12:34:56.789Z"
  }
}
```

### 7) UnequipAllCharacters

Unequips items from **all characters** (clears equipment slots everywhere).

**Request**

**Route** `POST /api/v2/{TitleTemplateId}/{TitleId}/Client/Character/UnequipAllCharacters/{UserID}`

**Body**

```json
{}
```

#### Response (OperationResult\<SuccessResponse>)

```json
{
  "Success": true,
  "Error": null,
  "Data": {
    "IsCompleted": true,
    "ServerTime": "2026-02-09T12:34:56.789Z"
  }
}
```

***

## Models (Contracts)

### CharacterRequest

```json
{
  "CharacterID": "string",
  "StatID": "string",
  "ItemsToEquip": [
    { "SlotID": "string", "ItemInstanceID": "string" }
  ],
  "UnequipSlotIDs": ["string"]
}
```

### EquipSlotPair

```json
{
  "SlotID": "string",
  "ItemInstanceID": "string"
}
```

### GetCharactersResponse

```json
{
  "Characters": {
    "<CharacterID>": {
      "CharacterID": "string",
      "Class": "string",
      "Name": "string",
      "Level": 0,
      "Experience": 0,
      "Power": 0,
      "StatLevels": { "<StatID>": 0 },
      "Equipment": {
        "<SlotID>": {
          "SlotID": "string",
          "CatalogVersion": "string",
          "ItemID": "string",
          "ItemInstanceID": "string",
          "EquippedAt": "2026-02-09T12:34:56.789Z"
        }
      },
      "UpdatedAt": "2026-02-09T12:34:56.789Z"
    }
  }
}
```

### CharacterModel

Represents a single owned character (stored inside `GetCharactersResponse.Characters`).

```json
{
  "CharacterID": "string",
  "Class": "string",
  "Name": "string",
  "Level": 0,
  "Experience": 0,
  "Power": 0,
  "StatLevels": { "<StatID>": 0 },
  "Equipment": {
    "<SlotID>": {
      "SlotID": "string",
      "CatalogVersion": "string",
      "ItemID": "string",
      "ItemInstanceID": "string",
      "EquippedAt": "2026-02-09T12:34:56.789Z"
    }
  },
  "UpdatedAt": "2026-02-09T12:34:56.789Z"
}
```

### EquippedItem

Equipment entry in `CharacterModel.Equipment`.

```json
{
  "SlotID": "string",
  "CatalogVersion": "string",
  "ItemID": "string",
  "ItemInstanceID": "string",
  "EquippedAt": "2026-02-09T12:34:56.789Z"
}
```

### ItemOrCurrency

Universal representation of currency or item costs/rewards.

```json
{
  "Type": "Item",
  "Catalog": "Items", //CatalogVersion, CatalogName
  "Amount": 10,
  "ImagePath": "icons/items/hero_1_shard.png",
  "Name": "Hero Shard",
  "CurrencyID": null,
  "ItemID": "hero_1_shard"
}
```

#### ItemType

```json
"Item" | "VirtualCurrency"
```

**Notes**

* When `Type = "VirtualCurrency"`: use `CurrencyID`, `Amount`
* When `Type = "Item"`: use `Catalog`, `ItemID`, `Amount`
* `Name` and `ImagePath` are optional UI helpers (may be null)

### UpgradeStatLevelResponse

```json
{
  "StatID": "string",
  "StatLevel": 0,
  "ConsumedResource": { /* ItemOrCurrency */ },
  "ConsumedResourceBalance": 0
}
```

### UpgradeCharacterLevelResponse

```json
{
  "CharacterID": "string",
  "NewLevel": 0,
  "ConsumedResources": [
    { /* ItemOrCurrency */ }
  ]
}
```

### SuccessResponse

```json
{
  "IsCompleted": true,
  "ServerTime": "2026-02-09T12:34:56.789Z"
}
```

***

## Client Flow (Recommended)

### A) Bootstrap (Open Character Screen)

1. `GetCharacterDefinitions`
2. `GetUserCharacters`

### B) Unlock / Rank Up

1. `UpgradeCharacterLevel` (`CharacterID`)
2. `GetUserCharacters`

### C) Upgrade Stat

1. `UpgradeStatLevel` (`CharacterID`, `StatID`)
2. `GetUserCharacters`

### D) Equip / Unequip

1. `EquipItems` / `UnequipItems`
2. `GetUserCharacters`

***

## Config Usage Guide (Client)

### Stat Resolution (Custom → Global)

When rendering or calculating a stat:

1. If `CustomStatDefinitions` contains `CharacterID` and has matching `StatID`, use that `StatDefinition`.
2. Otherwise use the matching entry from `StatDefinitions`.

### Level Resolution (Custom → Global)

When resolving upgrade cost or multipliers for a character level:

1. Try `CustomLevelDefinitions[CharacterID]` for the target `Level`.
2. Otherwise use `LevelDefinitions`.

### Effective Max Stat Level

To display the true stat cap for a character:

* `BaseMaxLevel = StatDefinition.MaxLevel`
* `Multiplier = CharacterLevelDefinition.StatMaxLevelMultiplier` for current character `Level`
* `EffectiveMaxLevel = floor(BaseMaxLevel * Multiplier)`

***

## Examples (cURL)

### GetCharacterDefinitions

```bash
curl -X POST "https://<host>/api/v2/<TitleTemplateId>/<TitleId>/Client/Character/GetCharacterDefinitions/<UserID>" \
  -H "Authorization: Bearer <SessionTicket>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

### GetUserCharacters

```bash
curl -X POST "https://<host>/api/v2/<TitleTemplateId>/<TitleId>/Client/Character/GetUserCharacters/<UserID>" \
  -H "Authorization: Bearer <SessionTicket>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

### UpgradeStatLevel

```bash
curl -X POST "https://<host>/api/v2/<TitleTemplateId>/<TitleId>/Client/Character/UpgradeStatLevel/<UserID>" \
  -H "Authorization: Bearer <SessionTicket>" \
  -H "Content-Type: application/json" \
  -d "{\"CharacterID\":\"main\",\"StatID\":\"Power\"}"
```

### UpgradeCharacterLevel

```bash
curl -X POST "https://<host>/api/v2/<TitleTemplateId>/<TitleId>/Client/Character/UpgradeCharacterLevel/<UserID>" \
  -H "Authorization: Bearer <SessionTicket>" \
  -H "Content-Type: application/json" \
  -d "{\"CharacterID\":\"hero_1\"}"
```

### EquipItems

```bash
curl -X POST "https://<host>/api/v2/<TitleTemplateId>/<TitleId>/Client/Character/EquipItems/<UserID>" \
  -H "Authorization: Bearer <SessionTicket>" \
  -H "Content-Type: application/json" \
  -d "{\"CharacterID\":\"main\",\"ItemsToEquip\":[{\"SlotID\":\"Weapon\",\"ItemInstanceID\":\"I_001\"}]}"
```

### UnequipItems

```bash
curl -X POST "https://<host>/api/v2/<TitleTemplateId>/<TitleId>/Client/Character/UnequipItems/<UserID>" \
  -H "Authorization: Bearer <SessionTicket>" \
  -H "Content-Type: application/json" \
  -d "{\"CharacterID\":\"main\",\"UnequipSlotIDs\":[\"Weapon\"]}"
```
