Skip to content
Ensoul

API Reference

The Ensoul API lets you create personas, talk to them on behalf of your own end users, place them in worlds, ask many personas at once, and run simulations.

API access requires authentication via API key or OAuth. See the SDKs page for official client libraries.

The hosted API base URL is https://api.ensoul-ai.com. All endpoint paths below are versioned under /v1 (for example, https://api.ensoul-ai.com/v1/personas).

Authentication

Ensoul supports two authentication methods:

Google OAuth (UI)

For browser-based access through Ensoul Studio. Authentication is handled automatically via session cookies after Google sign-in.

API Keys (Programmatic)

For server-to-server communication. API keys are created in the Studio UI under Settings → API Keys and belong to your team.

Using API Keys

Pass your API key in the X-API-Key header (OAuth users send a JWT in the Authorization: Bearer header instead):

bash
curl -X GET "https://api.ensoul-ai.com/v1/api/me" \
-H "X-API-Key: YOUR_API_KEY"
GET/v1/api/me

Your plan's current limits, and the key you used. key.kind is full for a team key, or persona for a key bound to one persona, with key.persona_id naming it.

Response
json
{
"tier": "...",
"personas": { "...": "..." },
"domains": { "...": "..." },
"credits": { "...": "..." },
"key": { "kind": "persona", "persona_id": "PERSONA_UUID" }
}
⚠

Security

Never expose API keys in client-side code. Revoke and rotate keys anytime in Settings → API Keys.

Hello World

Create a persona with no world, send it one message as one of your end users, and end the conversation.

# pip install ensoul
from ensoul import Ensoul

client = Ensoul(api_key="YOUR_API_KEY")

# A persona needs no world. This one stands alone.
persona = client.personas.create(name="Ada")

# user_id is your own id for the person talking.
# Each end user gets their own conversations with the persona.
reply = client.chat.send(
    persona.id,
    "Hi! Who are you?",
    user_id="YOUR_END_USER_ID",
)
print(reply.response)

# End the conversation when the person is done.
client.chat.end_conversation(
    persona.id,
    reply.conversation_id,
    user_id="YOUR_END_USER_ID",
)

Personas

Create and manage personas. Only a name is required: a persona created with no domain in the body has no world.

GET/v1/personas

List personas with pagination and filters (region, archetype, country, city).

POST/v1/personas

Create a persona. Add a world name as domain to place it in that world.

POST/v1/personas/batch

Create multiple personas at once with controlled distribution.

GET/v1/personas/:id

Get a specific persona with full trait profile.

GET/v1/personas/:id/personality

Get a persona's full personality vector.

Chat

1:1 conversations with individual personas, on behalf of your own end users. Every response is generated through real-time inference.

POST/v1/personas/:id/chat

Send a message to a persona and receive a response.

Request Body
json
{
"message": "What do you think about remote work?",
"user_id": "YOUR_END_USER_ID",
"conversation_id": "optional-conversation-uuid"
}
POST/v1/personas/:id/chat/stream

Stream a persona's response token-by-token via Server-Sent Events.

GET/v1/personas/:id/conversations

List a persona's conversations.

GET/v1/personas/:id/conversations/:conversationId

Get one conversation with its messages.

POST/v1/personas/:id/conversations/:conversationId/end

End a conversation. The persona files what it learned, and a later turn on it gets 409 conversation_ended. Returns 204 with no body.

Request Body (optional)
json
{
"user_id": "YOUR_END_USER_ID",
"reason": "done"
}
DELETE/v1/personas/:id/conversations/:conversationId

Delete a stored conversation. The persona's memories stay.

GET/v1/chat/sessions

List chat sessions.

GET/v1/chat/sessions/:id

Get a specific chat session with message history.

End Users

user_id is your own id for the person talking. Each end user gets their own conversations with a persona. Send the same user_id to chat, end and delete a conversation.

A key bound to one persona must send user_id on every conversation route, or the API answers 400 with user_id_required.

POST/v1/end-users/forget

Erase everything kept for one end user: their conversations and messages, and the memories personas formed from them.

Request Body
json
{
"user_id": "YOUR_END_USER_ID"
}
Response
json
{
"user_id": "YOUR_END_USER_ID",
"conversations_erased": 3,
"memories_erased": 12
}
⚠

Full keys only

Forget takes a team key. A key bound to one persona gets 403 with key_scope. A later turn as a forgotten end user gets 409 with end_user_forgotten.

Worlds

A world holds knowledge and relationships its personas share. A persona does not need one. The API calls a world a domain, so its paths and fields say domain.

GET/v1/domains

List the worlds your account can reach.

POST/v1/domains

Create a world (Studio has a guided wizard).

GET/v1/domains/:name

Get one world by name with its full configuration.

POST/v1/domains/generate

Generate a complete world from a text description.

POST/v1/domains/:id/personas/batch/stream

Grow a world's population from its archetypes and tiers, streaming each persona as they are made.

Residency

A persona lives in at most one world at a time, and moving keeps everything it has: its traits, memories and relationships go with it. A persona that already lives in a world is taken out of it before it can be brought into another.

POST/v1/personas/:id/place-in-world

Bring a persona into a world. It keeps everything it has, including whatever archetype and tier values it last had.

POST/v1/personas/:id/remove-from-world

Take a persona out of its world. It keeps its memories and relationships and lives on its own.

GET/v1/personas/:id/worlds

Every world a persona has lived in, newest first.

Networks

A network is a named set of personas who know each other. It belongs to your team, spans worlds, and can hold personas who live in none.

GET/v1/networks

List your team's networks.

POST/v1/networks

Create a network.

GET/v1/networks/:id

Get one network with its members.

PATCH/v1/networks/:id

Rename a network or change its description.

DELETE/v1/networks/:id

Delete a network. Its personas and the ties between them are kept.

POST/v1/networks/:id/members

Add personas to a network.

DELETE/v1/networks/:id/members/:personaId

Take one persona out of a network.

GET/v1/networks/:id/graph

The network's personas and the ties between them.

Ask Many Personas

Ask many personas the same question at once and read their answers together.

POST/v1/aggregate/stream

Stream answers from many personas.

POST/v1/aggregate/stream/grouped

Stream grouped aggregate results (by archetype or tier).

GET/v1/aggregate/count

Count the personas your filters match.

Simulations

Run multi-step simulations where personas interact and evolve over time.

GET/v1/simulations

List all simulations.

POST/v1/simulations

Create a simulation. Its personas come from one source: a world (domain_id), a network (network_id), or a list you name.

GET/v1/simulations/:id

Get simulation details and status.

POST/v1/simulations/:id/start

Start a simulation.

POST/v1/simulations/:id/pause

Pause a running simulation.

PATCH/v1/simulations/:id/speed

Adjust simulation speed.

GET/v1/simulations/:id/stream

Stream real-time simulation events (SSE).

GET/v1/simulations/:id/history

Get simulation interaction history.

GET/v1/simulations/:id/checkpoints

List simulation checkpoints.

POST/v1/simulations/:id/checkpoints/:cpId/restore

Restore simulation to a checkpoint.

Persona Memories

Manage persona long-term memories and batch memory generation.

GET/v1/memory/:personaId

List memories for a persona.

POST/v1/memory/:personaId/generate

Generate memories for a persona (using bloom modes).

POST/v1/memory/:personaId/batch

Batch create memories.

DELETE/v1/memory/:personaId/:memoryId

Delete a specific memory.

POST/v1/domains/:id/memories/generate-batch

Batch generate memories for every persona in a world.

GET/v1/domains/:id/memories/bloom-jobs

List bloom (memory generation) jobs.

POST/v1/domains/:id/memories/bloom-jobs/:jobId/cancel

Cancel a running bloom job.

Frameworks & Validation

Manage personality frameworks and run validation experiments.

GET/v1/frameworks

List available personality frameworks.

GET/v1/frameworks/:id

Get framework details including traits and grounding.

GET/v1/frameworks/:id/traits/:traitName/grounding

Get/update trait grounding text.

POST/v1/frameworks/:id/auto-tune

AI-generate improved trait grounding.

GET/v1/validations

List validation runs.

POST/v1/validations

Start a new validation experiment.

GET/v1/validations/:id

Get validation results.

GET/v1/validations/:id/tuning-report

Get auto-tune report for a validation.

Error Handling

The API uses standard HTTP status codes. The body is a flat JSON object with a request_id. On a typed refusal error is the code, with its fields beside it. On a plain HTTP error error names the status and message explains it.

json
{
"error": "conversation_ended",
"conversation_id": "conversation-uuid",
"request_id": "req_abc123"
}

Status Codes

CodeMeaning
200Success
201Created
400Bad Request: invalid parameters, or user_id_required when a key bound to one persona sends no user_id
401Unauthorized: invalid or missing credentials
403Forbidden: insufficient permissions, or key_scope when a key bound to one persona calls a team-only route
402Payment Required: a monthly limit is reached, such as a persona's calls this month (persona_calls)
404Not Found: the resource doesn't exist
409Conflict: turn_in_flight, conversation_ended or end_user_forgotten
429Rate Limited: too many requests
500Server Error: something went wrong

Rate Limits

Rate limits and monthly limits depend on your plan. GET /v1/api/me returns your current limits. A request over the rate limit gets 429.

Next Steps