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_idstring✓Opaque 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.
personaobject✓The character. See below.
messagesarray✓1–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
namestring✓1–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 run — inbound_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.