Custom Characters
Custom Characters let you define an NPC once in the dashboard and call it from your game by a stable characterId slug. The character stores its name, personality, physical description, optional avatar, and conversation analytics.
Use Custom Characters when the NPC should be managed outside your game build, reused across clients, and visible in the dashboard conversation log.
Use inline chat when the NPC description is generated dynamically by your own server or game state.
Create a character
- Open a project in the dashboard.
- Open Characters.
- Create a character with:
Name: display name in the dashboard.Character ID: developer-facing slug, for examplesilas_merchant.Personality: how the character behaves and speaks.Physical description: appearance and relevant visual context.Avatar: optional PNG, JPG, or WEBP up to 5 MB.
- Save the character.
characterId rules:
- 3-64 characters.
- Lowercase letters, numbers, hyphens, and underscores.
- Must start with a letter or number.
- Unique within one project.
Endpoint choices
| Use case | Endpoint |
|---|---|
| Server calls with an API key and inline description | POST /v1/chat |
| Server calls with an API key and dashboard character | POST /v1/chat/character |
| Game client calls with player JWT and inline description | POST /v1/chat/player |
| Game client calls with player JWT and dashboard character | POST /v1/chat/player/character |
Pre-v1 endpoints still work during the deprecation window, but new integrations should use /v1.
Server API example
Call a dashboard character from your own backend with a project API key:
curl -X POST https://api.journale.ai/v1/chat/character \
-H "Authorization: Bearer $JOURNALE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"characterId": "silas_merchant",
"message": "What do you have for sale today?",
"context": "The player enters a rainy market square.",
"playerDescription": "A tired ranger carrying a cracked lantern."
}'Successful response:
{
"reply": "A wet traveler, eh? I have dry tinder, lantern oil, and a map that hates the rain less than most.",
"character": {
"characterId": "silas_merchant"
},
"usage": {
"prompt_tokens": 143,
"completion_tokens": 31,
"total_tokens": 174
}
}If the character does not exist in the API key's project, the API returns:
{ "error": "character_not_found" }No credits are consumed for an unknown character because the character lookup happens before the LLM call.
Unity SDK
Upgrade to the Unity SDK version that includes /v1 paths and ChatWithCharacter(...).
Configure paths
New SessionConfig assets use:
sessionCreatePath: /v1/sessions
chatPath: /v1/chat/player
characterChatPath: /v1/chat/player/characterIf you already have a SessionConfig.asset, update those fields manually or create a fresh config from Project Settings.
Inline NPC chat
Use ChatToAi(...) when the character description comes from your game code:
using JournaleClient;
using UnityEngine;
public class InlineNpc : MonoBehaviour
{
async void Talk()
{
string reply = await Journale.ChatToAi(
localId: "guard_01",
message: "Can I enter the city?",
characterDescription: "A suspicious gate guard with no patience for jokes.",
playerDescriptionOverride: "A traveler with muddy boots."
);
Debug.Log(reply);
}
}Dashboard character chat
Use ChatWithCharacter(...) when the character exists in the Journale dashboard:
using JournaleClient;
using UnityEngine;
public class StoredCharacterNpc : MonoBehaviour
{
async void Talk()
{
string reply = await Journale.ChatWithCharacter(
characterId: "silas_merchant",
message: "What do you have for sale today?",
context: "The player is standing at Silas's market stall.",
playerDescription: "A ranger with a damaged lantern."
);
Debug.Log(reply);
}
}The Unity SDK creates a player session with POST /v1/sessions, signs requests, and sends stored-character messages to POST /v1/chat/player/character.
ChatToAi(...) no longer accepts dashboard characterId values. Use ChatWithCharacter(...) for stored characters.
Roblox SDK
Upgrade to the Roblox SDK version that includes /v1 paths and Journale.ChatWithCharacter.
Initialize the SDK
local Journale = require(game.ServerScriptService.JournaleSDK)
Journale.Init({
projectId = "YOUR_PROJECT_ID",
secretName = "JournaleAPIKey",
-- apiKey = "jr_...", -- Studio testing only
})Inline NPC chat
Use ChatToAi when personality is passed in the request:
local result = Journale.ChatToAi(player, "shopkeeper_local", "What do you sell?", {
characterDescription = "A cheerful village shopkeeper who knows local gossip.",
playerDescriptionOverride = "A new player visiting the market.",
customPlayerData = {
level = 7,
faction = "Knights of Dawn",
},
})
if result.success then
print(result.reply)
else
warn(result.error)
endDashboard character chat
Use ChatWithCharacter when the character is stored in the dashboard:
local result = Journale.ChatWithCharacter(player, "silas_merchant", "What do you sell?", {
playerDescriptionOverride = "A new player visiting the market.",
customPlayerData = {
level = 7,
faction = "Knights of Dawn",
},
})
if result.success then
print(result.reply)
else
warn(result.errorCode, result.error)
endThe Roblox SDK sends dashboard character calls to POST /v1/chat/character with the player's Roblox ID in external_id, identifier_type = "roblox", and collected player_data.
Analytics and logs
Stored-character endpoints write to the character conversation log after credits are deducted:
/v1/chat/character/v1/chat/player/character
Inline endpoints do not write to per-character analytics:
/v1/chat/v1/chat/player
The dashboard shows:
- Total messages.
- Unique players.
- Live users from the last 5 minutes.
- Last activity.
- Recent conversation turns.
Full turn content is retained for 90 days. Daily aggregate stats are retained indefinitely.
Common errors
| Error | Meaning |
|---|---|
invalid_body | Request body failed validation. |
unexpected_characterDescription | Stored-character endpoints do not accept inline character descriptions. |
unexpected_characterId | Inline endpoints do not accept dashboard character IDs. |
unexpected_playerId | Player-session stored-character calls get player identity from the JWT only. |
character_not_found | No matching characterId exists in the authenticated project. |
player_identity_conflict | playerId and resolved external_id point to different players. |
insufficient_credits | The project owner does not have enough available credits. |