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

  1. Open a project in the dashboard.
  2. Open Characters.
  3. Create a character with:
    • Name: display name in the dashboard.
    • Character ID: developer-facing slug, for example silas_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.
  4. 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 caseEndpoint
Server calls with an API key and inline descriptionPOST /v1/chat
Server calls with an API key and dashboard characterPOST /v1/chat/character
Game client calls with player JWT and inline descriptionPOST /v1/chat/player
Game client calls with player JWT and dashboard characterPOST /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/character

If 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)
end

Dashboard 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)
end

The 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

ErrorMeaning
invalid_bodyRequest body failed validation.
unexpected_characterDescriptionStored-character endpoints do not accept inline character descriptions.
unexpected_characterIdInline endpoints do not accept dashboard character IDs.
unexpected_playerIdPlayer-session stored-character calls get player identity from the JWT only.
character_not_foundNo matching characterId exists in the authenticated project.
player_identity_conflictplayerId and resolved external_id point to different players.
insufficient_creditsThe project owner does not have enough available credits.