Skip to main content

Chat

Generate a single in-character reply for a persona and a recent message buffer. Phantom Router retrieves relevant memories from your vector store, injects them into the prompt, runs inbound moderation on the user's message, and (on a cadence) extracts new memories in the background.

POST /api/v1/companion/chat
  • Auth: Bearer key with the chat:companion scope.
  • Requires: a configured vector store (otherwise 400).
Build a request in the Playground

Construct a chat request interactively and watch the response come back in the Playground - edit the persona and per-turn options, send a turn, then copy the exact request and response JSON straight into your own code.

Request body

FieldTypeRequiredNotes
chat_idstringOpaque per-user conversation id. Pattern ^[A-Za-z0-9][A-Za-z0-9_.:@-]{0,127}$, max 128 chars. Reuse it across turns to accumulate memory.
personaobjectThe character. See below.
messagesarray1–100 turns. The last message must be role: "user".
extract_memoryboolean-Default true. Set false to skip background memory write-back (retrieval still runs).
note

temperature and max_tokens are not request parameters. Generation settings are configured per endpoint by the Phantom Router team and applied to every call.

persona

Every field describes the companion character (e.g. Luna), not the user. All fields except name are optional free text.

FieldTypeRequiredNotes
namestring1–120 chars. The character's display name.
nicknamestring-Up to 120 chars. What the user calls the character.
genderstring-Up to 200 chars.
date_of_birthstring-Up to 200 chars. Free text — a date, an age, or a range.
languagestring-Up to 200 chars, e.g. "English, Portuguese".
lives_in_countrystring-Up to 200 chars.
lives_in_citystring-Up to 200 chars.
comes_from_countrystring-Up to 200 chars.
comes_from_citystring-Up to 200 chars.
living_situationstring-Up to 2000 chars.
daily_routinestring-Up to 2000 chars.
goalsstring-Up to 2000 chars.
biostring-Up to 4000 chars. Background, personality, backstory.
boundariesstring-Up to 2000 chars. Lines the character will not cross.
chat_stylestring-Up to 80 chars, e.g. "playful, teasing". Slots into the generated preamble.
custom_instructionsstring-Up to 8000 chars. Appended as a strong directive; does not replace the preamble.

The persona is stateless - send it on every turn. Phantom Router doesn't store it.

messages

Each item is { "role": "user" | "assistant", "content": "..." }, with content 1–16000 chars. Send a rolling window of recent turns; the last one must be from the user.

Example request

curl -X POST https://api.phantomrouter.ai/api/v1/companion/chat \
-H "Authorization: Bearer $PHANTOM_KEY" \
-H "Content-Type: application/json" \
-d '{
"chat_id": "user-42",
"persona": {
"name": "Luna",
"bio": "A warm, witty companion who loves astronomy.",
"chat_style": "playful, curious"
},
"messages": [
{ "role": "user", "content": "Morning! Remember I had that big presentation?" }
]
}'

Response

The response is a conversation chain: your request messages echoed back with the in-character reply appended as the final assistant turn. Append it to your buffer for the next turn.

{
"chat_id": "user-42",
"messages": [
{ "role": "user", "content": "Morning! Remember I had that big presentation?" },
{
"role": "assistant",
"content": "Morning, you! Of course - the big one you were dreading. How'd it land?"
}
],
"moderation": { "enabled": true, "inbound_prohibited": false }
}
FieldTypeNotes
chat_idstringEchoes the request.
messagesarrayThe request buffer with the reply appended. The last turn (role: "assistant") is the new reply.
moderation.enabledbooleantrue when the inbound check ran (an Omnifence key is configured). false means it was not runinbound_prohibited is then always false because nothing was checked, not because the content passed.
moderation.inbound_prohibitedbooleantrue if the user's message was refused. Only inbound (user-supplied) content is moderated; the generated reply is never checked.
moderation.reasonstring?Present when moderation acted.

Reading the result: enabled: false → moderation didn't run (connect an Omnifence key to turn it on). enabled: true with inbound_prohibited: false → ran and passed. inbound_prohibited: true → the user's turn was blocked.

Memory retrieval and extraction still happen automatically on every eligible turn — they're just not surfaced in the response body. To see exactly what was retrieved and whether extraction fired, use the Playground, which renders the full pipeline.

Moderation behavior

Moderation is opt-in and inbound-only: screening runs on the user-supplied message buffer only when you have connected an Omnifence API key (in the console, under Chat). The generated reply is never moderated. Without a key, moderation.inbound_prohibited is always false. When enabled, moderation runs inline and returns 200, not an error:

  • Inbound refusal - if the user's message is prohibited, the appended assistant turn is a canned refusal, moderation.inbound_prohibited is true, and nothing is generated or extracted.

Memory & cadence

Retrieval runs on every turn. Extraction is cadence-gated - by default Phantom Router extracts new memories every few user turns, not on every message, and the job runs in the background. Set extract_memory: false to opt out of write-back for a turn while still retrieving.

Errors

StatusCodeWhen
400INVALID_REQUESTBody failed validation, or no vector store is configured.
401UNAUTHORIZEDMissing or invalid key.
402PAYMENT_REQUIREDInsufficient credit balance.
403FORBIDDENKey lacks chat:companion.
503SERVICE_UNAVAILABLETransient model-provider or vector-store failure. Retry with backoff.