Build with the AI
Personas
Attach one of your AI personas to completions, and manage personas over the API.
A persona is a named, reusable system prompt (plus optional sampling defaults) that shapes how the model responds. There are two things you can do with personas through the API:
- Use a persona in chat completions — its
system_promptis prepended to every request, so you don’t have to resend a long system message each time. - Manage personas programmatically — create, read, update, and delete them under
/api/v1/personas.
Attach a persona to completions
When a persona is in effect, its system_prompt is inserted as a leading
system message ahead of the messages you send to
/api/v1/chat/completions. The persona text is only ever sent upstream to the model — it is
never echoed back in the response body. There are two ways to select the persona for a
request, in priority order.
Pin a persona to your app
Open the Developer console at /ops/dev, edit your app, and set its
AI persona. Once pinned, that persona is applied to every
/chat/completions call made with any key belonging to that app — no per-request field
required. Leave it unset for no persona (the default).
If a pinned persona is later deleted, calls do not fail — the stale default is silently skipped and the request runs with no persona. Only an explicit per-request persona that isn’t usable is an error (see below).
Override per request
Add a persona field to the completion body, set to the integer id of the persona. A
per-request persona overrides whatever is pinned to the app for that one
call.
curl https://exe.krallo.com/api/v1/chat/completions \
-H "Authorization: Bearer krallo_live_..." \
-H "Content-Type: application/json" \
-d '{
"model": "krallo-chat",
"persona": 42,
"messages": [{"role": "user", "content": "Draft a one-line release note."}]
}'
The response is a standard OpenAI chat.completion object, unchanged in shape whether or not
a persona is attached. See Chat completions for the full
request and response reference.
Which personas you can use
A persona is usable in a completion only if it is one of the following:
- Your own — a persona you created (matched across all of your linked identities, not just the app’s canonical owner id).
- A built-in SYSTEM persona — the platform’s shared personas, available to everyone.
- An owner-less global persona.
Guild-scoped personas (owned by a server rather than a user) are deliberately not usable
through the developer API. If you send a persona id that doesn’t exist or isn’t
one of the above, the request returns 400 and the model is never run (and you
are not billed for it):
{
"error": {
"message": "Persona '42' was not found or does not belong to you.",
"type": "invalid_request_error"
}
}
What attaching a persona changes
Attaching a persona affects only the system prompt. Specifically:
| Behavior | Effect of attaching a persona |
|---|---|
| System prompt | The persona’s system_prompt is prepended as a leading system message. If your first message is already a system message, the persona text is merged in ahead of it. |
| Length | The prompt is clamped to PROMPT_MAX_PERSONA_SYSTEM_CHARS characters (default 6000) at completion time. Anything longer is truncated with a short marker, so a giant persona can’t starve your conversation budget. You can still store a system_prompt up to 12,000 characters (see limits below). |
Sampling (temperature, max_tokens, …) | Not applied. A persona’s stored sampling fields are for the persona record itself; developer-API completions do not inherit them. Set temperature / max_tokens on the completion request body if you want to tune sampling. |
| Model | Unaffected — the model is chosen by the request’s model field only. |
Manage personas over the API
The native /api/v1/personas resource is standard REST returning the Krallo envelope
({ "data": …, "meta": { "request_id": … } }), not the OpenAI wire format. Reads require the
personas:read scope; writes require
personas:write. Each call is flat-metered against your wallet — see
Billing & quotas.
| Method & path | Scope | Description |
|---|---|---|
GET /api/v1/personas | personas:read | List the personas you own. |
POST /api/v1/personas | personas:write | Create a persona. |
GET /api/v1/personas/{id} | personas:read | Retrieve one persona. |
PATCH /api/v1/personas/{id} | personas:write | Partially update a persona (PUT is also accepted). |
DELETE /api/v1/personas/{id} | personas:write | Delete a persona. |
“Usable” is broader than “manageable.” The CRUD endpoints operate only on personas you own. Built-in SYSTEM and global personas are usable in completions but do not appear in GET /api/v1/personas and cannot be updated or deleted — attempting to do so returns 404.
The persona object
Every persona endpoint returns objects with this shape:
{
"id": 128,
"name": "Release Notes Writer",
"description": "House voice for changelog entries",
"system_prompt": "You write concise, factual release notes...",
"icon_emoji": "📝",
"temperature": 0.4,
"max_tokens": 1200,
"top_p": null,
"frequency_penalty": null,
"presence_penalty": null,
"locked": false,
"message_count": 0
}
| Field | Type | Description |
|---|---|---|
id | integer | Persona id. Read-only; use it as the persona value on completions and in the path. |
name | string | Display name. |
description | string · nullable | Short human note. Not sent to the model. |
system_prompt | string | The instruction text prepended to completions. |
icon_emoji | string · nullable | Emoji used in the console UI. |
temperature | number · nullable | Stored sampling default (0–2). Not applied by developer-API completions. |
max_tokens | integer · nullable | Stored default (1–32000). Not applied by developer-API completions. |
top_p | number · nullable | Stored nucleus-sampling default (0–1). |
frequency_penalty | number · nullable | Stored default (−2–2). |
presence_penalty | number · nullable | Stored default (−2–2). |
locked | boolean | Read-only. A locked persona cannot be edited or deleted (returns 409). |
message_count | integer | Read-only usage counter. |
Create a persona
/api/v1/personas
name and system_prompt are required; every other field is optional. The request
is validated before it is billed, so a rejected create costs nothing. On success you get
201 with the full persona object.
curl https://exe.krallo.com/api/v1/personas \
-H "Authorization: Bearer krallo_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Release Notes Writer",
"system_prompt": "You write concise, factual release notes. No hype.",
"description": "House voice for changelog entries",
"icon_emoji": "📝",
"temperature": 0.4,
"max_tokens": 1200
}'
{
"data": {
"id": 128,
"name": "Release Notes Writer",
"description": "House voice for changelog entries",
"system_prompt": "You write concise, factual release notes. No hype.",
"icon_emoji": "📝",
"temperature": 0.4,
"max_tokens": 1200,
"top_p": null,
"frequency_penalty": null,
"presence_penalty": null,
"locked": false,
"message_count": 0
},
"meta": { "request_id": "req_..." }
}
Update a persona
/api/v1/personas/{id}
Updates are partial: send only the fields you want to change, and any field you omit is
left unchanged. (There is no way to reset an advanced field back to null through the API —
omitting it means “keep the current value.”) The same length and range rules as create apply
to whatever you do send. PUT behaves identically. On success you get
200 with the updated object.
curl -X PATCH https://exe.krallo.com/api/v1/personas/128 \
-H "Authorization: Bearer krallo_live_..." \
-H "Content-Type: application/json" \
-d '{ "temperature": 0.2, "system_prompt": "You write terse, factual release notes." }'
The model, locked, and message_count fields are read-only. Any model value in the body is ignored — model selection for completions is controlled by the request’s model field, not the persona.
List, retrieve, and delete
GET /api/v1/personas returns an array (in data) of the personas you own;
GET /api/v1/personas/{id} returns a single object, or 404 if the id
is unknown or not yours. Deleting returns a small confirmation:
/api/v1/personas/{id}{
"data": { "id": 128, "deleted": true },
"meta": { "request_id": "req_..." }
}
Locked personas
A persona with "locked": true is protected. A PATCH/PUT or
DELETE against it returns 409 with code
persona_locked and leaves the persona untouched. Locked personas are still usable in
completions.
Field limits and validation
Validation runs before billing on both create and update. A violation returns
400 with code invalid_request and a message naming the offending
field. A present-but-wrong-typed value (for example a string where a number is expected) is rejected
rather than silently dropped.
| Field | Type | Required | Constraint |
|---|---|---|---|
name | string | Yes (create) | Non-blank, at most 120 characters. |
system_prompt | string | Yes (create) | Non-blank, at most 12,000 characters stored. Clamped to ~6,000 at completion time. |
description | string | No | At most 600 characters. |
icon_emoji | string | No | At most 16 characters. |
temperature | number | No | Between 0 and 2. |
max_tokens | integer | No | Between 1 and 32,000. |
top_p | number | No | Between 0 and 1. |
frequency_penalty | number | No | Between −2 and 2. |
presence_penalty | number | No | Between −2 and 2. |
Errors
Persona endpoints can return the following. See Errors for the full catalog and envelope details.
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | CRUD validation failed (missing/blank name or system_prompt, a field over its length limit, or a value out of range / wrong type). |
| 400 | invalid_request_error | Completions only: an explicit persona id that doesn’t exist or isn’t usable by you. |
| 401 | unauthorized · invalid_token | Missing or invalid bearer key. See Authentication. |
| 403 | insufficient_scope | The key lacks personas:read (reads) or personas:write (writes). See Scopes. |
| 404 | not_found | The persona id is unknown, or it isn’t one you own (including SYSTEM/global personas on write). |
| 405 | method_not_allowed | Unsupported method for the path. |
| 409 | persona_locked | Attempted to edit or delete a locked persona. |
| 429 | rate_limited | Per-key rate limit exceeded. See Rate limits. |
| 500 | internal_error | Unexpected server error; the metered call is refunded. |
Related pages
- Personas API reference — full parameter tables per endpoint.
- Chat completions — the endpoint personas attach to.
- Scopes —
personas:readandpersonas:write. - Memories — the other half of programmatic AI configuration.
- Billing & quotas — how metered calls are charged.