A character card is a JSON object that your frontend turns into one system message and resends on every request. The portrait, the library and the swipe button are packaging. Once you see the card as a prompt, the format, the PNG trick and the cost all follow.
The field-by-field reference, with five cards to download, is the original post: SillyTavern character cards. Disclosure: it is ours, and so is the gateway used in the code below.
The fields
Character Card V2 (chara_card_v2) is the format every library ships and every frontend reads. The spec lists the fields. SillyTavern's Chat Completion preset sends them as one system message, in this order:
-
system_prompt: replaces the frontend's default instruction when set. -
description: who the character is, as prose the model reads every turn. -
scenario: where the chat starts and what{{user}}is doing there. -
mes_example: sample exchanges in<START>blocks. The model copies their voice, length and formatting.
first_mes is the opening assistant message, and it should end on something the user can answer. creator_notes, tags, creator and character_version are metadata. They never reach the model. {{char}} and {{user}} are replaced with names before the text is sent, so one card works for any persona.
PNG or JSON
They carry the same JSON. A PNG card stores it base64-encoded in a text chunk of the image: keyword chara for V1 and V2, ccv3 for V3. The pixels do not matter to the model. Image hosts and chat apps often strip text chunks, so a card that passes through one imports as a plain picture. Send the JSON, or attach the PNG as a file. Use JSON when you want to diff a card in git.
Read a downloaded card's JSON before you load it. A card can carry a system_prompt that overrides your preset.
Tokens are the cost
The card is resent with every message, so its length is a per-message charge for the whole chat. The usual advice is 600 to 1,000 tokens. A 2,000-token card adds twice as many input tokens to every message as a 1,000-token one. Put world history and the cast in a lorebook, which the frontend sends only when a keyword comes up.
This script builds the same request a frontend would and prints how much of the bill is prompt.
import json, os
from openai import OpenAI
client = OpenAI(base_url="https://api.uplinkswitch.com/v1",
api_key=os.environ["UPLINK_API_KEY"])
card = json.load(open("card.json"))["data"]
user = "Sam"
def fill(t): # {{char}} and {{user}} are swapped before the text goes out
return t.replace("{{char}}", card["name"]).replace("{{user}}", user)
default = "Write the next reply as {{char}} in a fictional chat with {{user}}."
system = "\n\n".join(fill(p) for p in [
card.get("system_prompt") or default,
card["description"], card["scenario"], card["mes_example"],
] if p)
resp = client.chat.completions.create(
model="roleplay-uncensored",
max_tokens=1000,
messages=[
{"role": "system", "content": system},
{"role": "assistant", "content": fill(card["first_mes"])},
{"role": "user", "content": "I knock twice."},
],
)
print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens) # the card is in here on every turn
Writing a card that holds
Treat it like any other prompt. Describe behavior, not adjectives: "talks in short sentences and notices shoes first" does more than "observant and laconic", because the model copies actions and only waves at traits. Two or three example exchanges in the exact register you want set the voice more than any instruction does. Say each fact once. A trait repeated in description, personality and scenario costs three times and makes the model repeat it. Then run ten turns and cut whatever the model never used.
Swap the model, keep the frontend
Janitor AI, Chub and SillyTavern keep the characters, personas and history. The model behind them writes each reply and sets the price. Character.AI is the other shape: you pick one of its Chat Styles, not a model.
-
SillyTavern: Chat Completion, source
Custom (OpenAI-compatible), endpointhttps://api.uplinkswitch.com/v1, your key, a model id. -
Janitor AI: Provider, Proxy, New. The proxy URL is
https://api.uplinkswitch.com/v1/chat/completions, with the key and model id below it.
Some builds think before they answer, and thinking counts against max tokens. Set SillyTavern's Max Response Length or Janitor's Max tokens to 1,000 or more for those. On 2026-10-18 roleplay-uncensored replied at once in SillyTavern, with no thinking step.
Try a card with no install
The free roleplay chat is a browser panel on the same builds. No sign-up, a few free turns a day, a prepaid key after that. It imports PNG or JSON cards (V1, V2 and V3), exports V2 JSON, and keeps your cards in your browser. The price of 100 messages sits next to each model in the picker.
Adults only, fiction only. No minors, no real people without consent.
Get a key: prepaid, no subscription, 30-day price lock, at uplinkswitch.ai/console.
Top comments (0)