Neocortex
IntegrationsWeb API

API Reference

Every endpoint of the Neocortex Web API

The Neocortex Web API is a set of HTTP endpoints for talking to your characters programmatically. This reference covers all of them: chat, streaming chat, session history, speech, streaming speech, transcription, quiz, account, usage and characters.

Endpoints

All endpoints live under one base URL:

https://api.neocortex.link/v3
MethodPathDescription
POST/chatTalk to one character or a whole cast.
POST/chat/sessionRead a session transcript, paged.
POST/audio/generateSpeak a line in the character's voice.
POST/audio/streamSpeak a line and receive the audio as it is generated.
POST/audio/transcribeTurn speech into text.
POST/quizRun a quiz hosted by one of your characters, one turn per call.
GET/quiz/sessionRead a quiz run back: standings, round history and timings.
GET/accountPlan, credits and next refresh.
GET/usageCredit status and per player or per character usage.
GET/charactersList the characters your key can use.

Using your API Key

With each request do not forget to send your API Key with the x-api-key header.

Headers x-api-key: YOUR_API_KEY

Keys are created in the dashboard and are scoped to your team. Every character you reference must belong to that team.


Chat with a character

One endpoint for a single character and for a group. Pass one id in characterIds for normal chat, or up to five for a group scene.

POST https://api.neocortex.link/v3/chat
{
  "characterIds": ["cmrkiw4f100017k7sfnicq08e"],  // Required, 1 to 5 characters
  "message": "Hey there!",                        // The player's line. Omit for an ambient turn
  "playerId": "player-001",                       // Optional, enables per player usage and limits
  "sessionId": "",                                // Optional, continue an existing conversation
  "speakerCharacterId": "",                       // Optional, force who answers this turn
  "playAs": null,                                 // Optional, a character the player role plays
  "maxTurns": 5,                                  // Optional, cap on speakers per turn (max 5)
  "metadata": [],                                 // Optional, what the character can perceive
  "events": []                                    // Optional, recent world events
}
{
  "sessionId": "cmdruuvyr0000jr04lteh35te",
  "messages": [
    {
      "characterId": "cmrkiw4f100017k7sfnicq08e",
      "name": "Aria",
      "lines": [
        { "text": "Good evening.", "emotion": "HAPPY" },
        { "text": "What can I get you tonight?", "emotion": "CURIOUS" }
      ],
      "actions": [{ "name": "POUR_DRINK", "targetId": "tankard-1" }],
      "flowState": ""
    }
  ],
  "metadata": []
}

Response fields

FieldDescription
sessionIdThe conversation. Send it back to continue, store it per player.
messages[]One entry per character that spoke, in the order they spoke.
messages[].characterIdWhich character spoke this entry.
messages[].nameThe character's display name, handy for labelling a transcript.
messages[].lines[]The reply as 1 to 3 ordered chunks, each with its own emotion.
messages[].actions[]Actions triggered this turn, each { name, targetId }, in the order the character intends them. [] when none fired.
messages[].flowStateThe Conversation Flow checkpoint this turn landed on, empty when unused.
metadataThe entities the characters perceived, echoed once for the scene, with isSubject marking whatever the actions targeted.

Replies always come as lines

lines is the reply. The character splits it into 1 to 3 chunks so you can drop them in one after another and change expression or voice per line. Join every text for the complete reply.

See Interactables for metadata, events and how actions[].targetId works, and Group Chat for speakerCharacterId, playAs and casts.


Get past messages

Reads a transcript oldest first, scoped to your team.

POST https://api.neocortex.link/v3/chat/session
{
  "sessionId": "cmdruuvyr0000jr04lteh35te",  // Required
  "limit": 20,                               // Optional, 1 to 100, default 20
  "before": ""                               // Optional, a previous nextCursor
}
{
  "sessionId": "cmdruuvyr0000jr04lteh35te",
  "messages": [
    {
      "content": "Hey there!",
      "sender": "USER",
      "speakerCharacterId": null,
      "name": null,
      "addressedTo": null,
      "emotion": null,
      "actions": [],
      "createdAt": "2026-07-18T10:00:00.000Z"
    },
    {
      "content": "Good evening. What can I get you tonight?",
      "sender": "ASSISTANT",
      "speakerCharacterId": "cmrkiw4f100017k7sfnicq08e",
      "name": "Aria",
      "addressedTo": "Player",
      "emotion": "HAPPY",
      "actions": ["POUR_DRINK"],
      "createdAt": "2026-07-18T10:00:02.000Z"
    }
  ],
  "nextCursor": "cmdruv1c40002jr04a1b2c3d4"
}

To page backwards through older messages, send the previous response's nextCursor as before. It is null once you reach the start of the conversation.

name and addressedTo are what make a group transcript readable: who spoke, and who they were speaking to.


Generate speech

Speaks a line in the character's configured voice. The character needs a Voice node, otherwise this returns 403 or 404.

POST https://api.neocortex.link/v3/audio/generate
{
  "characterId": "cmrkiw4f100017k7sfnicq08e",  // Required
  "message": "Good evening, traveller.",       // Required
  "emotion": "HAPPY",                          // Optional, colours the delivery
  "format": "wav"                              // Optional, audio format
}
Raw audio bytes.

This endpoint does not return JSON on success. Write the body straight to a
file or feed it to your audio player.

The character's Language node decides the spoken language automatically.


Stream speech

Speaks a line like /audio/generate, but the audio starts arriving while the rest of it is still being generated, so playback can begin in well under half the time.

POST https://api.neocortex.link/v3/audio/stream
{
  "characterId": "cmrkiw4f100017k7sfnicq08e",  // Required
  "message": "Good evening, traveller.",       // Required
  "emotion": "HAPPY"                           // Optional, colours the delivery
}
Raw audio bytes, sent in pieces as they are produced.

A WAV header arrives first, followed by 16-bit mono audio at 24 kHz. The
header declares an unknown length, because the length is not known when it
is sent, so play the audio as it arrives rather than waiting for the end.

X-Sample-Rate and X-Channels repeat the format as response headers.

Playing it as it arrives

Reading the whole body before playing works and gives you an ordinary WAV, but it throws away the point of the endpoint. Feed each piece to your audio player instead. The Unity SDK does this for you.


Transcribe audio

Send multipart/form-data, not JSON.

POST https://api.neocortex.link/v3/audio/transcribe
FieldTypeDescription
characterIdtextRequired. Its Language node picks the transcription language.
audiofileRequired. The recorded clip, for example a .wav.
curl -X POST https://api.neocortex.link/v3/audio/transcribe \
  -H "x-api-key: YOUR_API_KEY" \
  -F "characterId=cmrkiw4f100017k7sfnicq08e" \
  -F "audio=@recording.wav"
{ "response": "Hey, who are you and where am I?" }

Run a quiz turn

A whole quiz through one endpoint. The first call takes a character and a question set; every call after takes the session and whatever the player said. Order, retries, hints and scoring are authored on the set and applied server side.

POST https://api.neocortex.link/v3/quiz
{
  "sessionId": "",           // Continue a run. Omit on the first call
  "characterId": "",         // Required on the first call, the host of the quiz
  "questionSetId": "",       // Required on the first call, the set to play
  "playerId": "",            // Optional, your own id for this player
  "participants": [],        // Optional, up to 10 with unique ids. Omit for one player
  "participantId": "",       // Who spoke, once there is more than one player
  "say": "",                 // What the player said. "" is an explicit pass
  "optionId": "",            // The option they tapped
  "questionCount": 5,        // Optional, first call only. Questions in this run
  "selectionMode": "random"  // Optional, first call only. "sequential" or "random"
}
{
  "sessionId": "cmu9w20hi00527k9kpcoirzuw",
  "lines": [
    { "text": "Close! Think about the corners.", "emotion": "CURIOUS", "role": "reaction" },
    { "text": "What shape has three sides?", "emotion": "CURIOUS", "role": "question" }
  ],
  "expecting": "answer",
  "intent": "answer",
  "result": [{ "verdict": "INCORRECT", "score": 0, "participantId": "player-1" }],
  "leaderboard": [
    { "participantId": "player-1", "displayName": "Player", "totalScore": 0, "rank": 1 }
  ],
  "done": false
}

Response fields

FieldDescription
sessionIdAlways sent. Identifies which run this payload belongs to.
lines[]What the host says, each with its own emotion and a role of greeting, reaction, question or closing.
expectingWhat to collect next: answer, confirmation or nothing.
intentHow the player was read. Omitted on a turn where nobody had spoken.
questionThe question now on screen, with its scene keyword and any options. Sent on the turn it changes and not repeated after.
upcomingThe question after this one, for preloading its scene.
progress{ asked, total }. Travels with question.
result[]How the last answer was judged. score is what reached the standings.
leaderboard[]The standings, sent only when a score could have moved.
pending[]Who the round is still waiting on. Omitted for a single player.
donetrue once the host has signed off.

The answer key never reaches the player

Expected answers, rubrics, hint text and the rest of the queue stay on the server, and every point is derived from the question's own scoring table rather than from the host. See Quiz for the full flow, the error codes and multi player runs.


Read a quiz run

Standings, round history and timings for a run. Everything a game needs to rebuild its UI after a restart.

GET https://api.neocortex.link/v3/quiz/session?sessionId=SESSION_ID
{
  "state": {
    "phase": "collecting",
    "roundNumber": 2,
    "totalQuestions": 5,
    "questionsRemaining": 3,
    "participants": [{ "id": "player-1", "displayName": "Player", "type": "human" }],
    "activeQuestion": { "questionId": "q_mfx2k4_b913", "type": "free_text", "questionText": "And this one?", "hasHint": false },
    "answeredParticipantIds": [],
    "pendingParticipantIds": ["player-1"],
    "leaderboard": [{ "participantId": "player-1", "displayName": "Player", "totalScore": 100, "rank": 1 }],
    "startedAt": "2026-09-20T09:14:02.881Z",
    "finishedAt": null,
    "nextAction": "evaluate",
    "expecting": "answer"
  },
  "leaderboard": [],
  "roundHistory": [],
  "questionSet": { "name": "Shape School", "evaluationProtocol": "educational" },
  "startedAt": "2026-09-20T09:14:02.881Z",
  "finishedAt": null
}

state.expecting is the same value a turn carries, so a client whose turn response was lost can reopen the microphone instead of guessing. roundHistory[] gains a correctAnswer on each round once the run has finished.


Get account status

GET https://api.neocortex.link/v3/account
{
  "tier": "PRO",
  "email": "you@studio.com",
  "creditsRemaining": 8420,
  "nextRefresh": "2026-08-01T00:00:00.000Z"
}

nextRefresh is null when nothing is scheduled to refresh.


Get usage

Both query parameters are optional. Pass playerId to get that player's row, characterId to get that character's row.

GET https://api.neocortex.link/v3/usage?playerId=player-001&characterId=cmrkiw4f100017k7sfnicq08e
{
  "status": "ok",
  "creditsRemaining": 8420,
  "player": {
    "interactionsToday": 3,
    "creditsUsedAllTime": 47,
    "overLimit": false
  },
  "character": {
    "creditsUsedAllTime": 128,
    "overLimit": false
  }
}

status is ok, low or empty. overLimit reflects the per player and per character caps you configure in the dashboard. An unknown player returns zero usage rather than an error, so you can gate on it safely.


List characters

GET https://api.neocortex.link/v3/characters
{
  "characters": [
    { "id": "cmrkiw4f100017k7sfnicq08e", "name": "Aria" },
    { "id": "cmrkiwj2k00037k7sxnpvs9p3", "name": "Bram" }
  ]
}

Id and name only. Useful for building a character picker in your own tooling.

Errors

Failures return { "error": "..." } with an HTTP status:

StatusMeaning
401Missing or invalid API key.
403The character does not belong to your key's team, or your plan does not allow it (a cast of more than one character needs Pro or Team).
404Character, session or voice not found.
422The request is malformed: empty message, more than 5 characters, a speakerCharacterId that is not in the cast, a playAs that is also in the cast, or an input over its size limit.
429Daily allowance for dashboard and editor calls exhausted.
400Out of credits, or a request body that was not JSON.
409A quiz session already has a turn running, or the run has finished.

Quiz responses also carry a code alongside error, so a client can branch on the reason. See Quiz for the full list.

On this page