API reference

Personas API

Create, read, update and delete your AI personas.

A persona is a reusable bundle of instruction and sampling settings that shapes how the Krallo AI responds — a system_prompt that steers its behaviour plus the tuning knobs (temperature, max_tokens, top_p, and the two penalties). The /api/v1/personas endpoints let you manage your personas programmatically, so you can build, version and delete them from your own tooling. Attach a persona to an inference call to make the AI answer in that voice; see Chat completions.

Personas are owner-scoped. Every operation acts only on personas owned by the account behind your API key (linked Discord and Google identities are treated as the same owner). A persona that is not yours is indistinguishable from one that does not exist — both return 404 not_found.

The persona object

Every endpoint that returns a persona serializes it with exactly these fields:

FieldTypeDescription
idintegerStable identifier for the persona. Use it in the item-level paths.
namestringDisplay name. Always present.
descriptionstring · nullOptional free-text note. null when unset.
system_promptstringThe instruction text that steers the AI’s behaviour.
icon_emojistring · nullOptional emoji shown alongside the persona. null when unset.
temperaturenumber · nullSampling temperature, 0–2. null means the server default applies.
max_tokensinteger · nullResponse length cap, 1–32000. null means the server default applies.
top_pnumber · nullNucleus-sampling cutoff, 0–1. null means the server default applies.
frequency_penaltynumber · nullRepetition penalty, -2–2. null means the server default applies.
presence_penaltynumber · nullTopic-novelty penalty, -2–2. null means the server default applies.
lockedbooleanWhether the persona is locked. A locked persona cannot be updated or deleted through this API. Always present; defaults to false.
message_countintegerHow many messages have been exchanged under this persona. Read-only.

The underlying model is deliberately not part of the persona object and cannot be set through this API — model selection is governed by Krallo’s server-side routing. Responses use the native Krallo envelope: successful bodies are wrapped as { "data": …, "meta": { "request_id": … } }, and errors as { "error": { "code", "message", … }, "meta": … }. See Errors for the full contract.

Scopes

Each persona operation requires the matching scope on your API key:

OperationMethodScope
List personasGETpersonas:read
Retrieve a personaGETpersonas:read
Create a personaPOSTpersonas:write
Update a personaPATCHpersonas:write
Delete a personaDELETEpersonas:write

A request missing the required scope returns 403 insufficient_scope with the needed scope echoed in error.required_scope. Grant scopes when you mint a key in the Developer console; see Scopes and Authentication.

List personas

GET/api/v1/personas

Returns your personas as a JSON array. Only personas you own are included; personas belonging to other owners (or bound to a guild rather than a user) are never returned. Requires personas:read. There are no query parameters — the full owned set is returned.

Request

cURL
curl https://exe.krallo.com/api/v1/personas \
  -H "Authorization: Bearer krallo_live_..."

Response

{
  "data": [
    {
      "id": 412,
      "name": "Release Notes Writer",
      "description": "Terse, factual changelog voice.",
      "system_prompt": "You write concise, factual release notes. No marketing language.",
      "icon_emoji": "📝",
      "temperature": 0.4,
      "max_tokens": 800,
      "top_p": 1,
      "frequency_penalty": 0,
      "presence_penalty": 0,
      "locked": false,
      "message_count": 37
    }
  ],
  "meta": { "request_id": "5f3a91c0-b2d4-4e16-9a58-c07d2f4b8e31" }
}

An owner with no personas receives "data": [] with a 200 status.

Create a persona

POST/api/v1/personas

Creates a persona owned by your account and returns it. Requires personas:write. Responds 201 Created on success. Body is JSON. Unknown fields are ignored.

Body parameters

NameTypeRequiredDescription
name string Yes Display name. Trimmed; must be non-blank and at most 120 characters.
system_prompt string Yes Instruction text that steers the AI. Must be non-blank and at most 12,000 characters.
description string No Free-text note. At most 600 characters.
icon_emoji string No Emoji to display with the persona. At most 16 characters.
temperature number No Sampling temperature. Must be between 0 and 2.
max_tokens integer No Response length cap. Must be between 1 and 32000.
top_p number No Nucleus-sampling cutoff. Must be between 0 and 1.
frequency_penalty number No Repetition penalty. Must be between -2 and 2.
presence_penalty number No Topic-novelty penalty. Must be between -2 and 2.

Omit any optional field to leave it unset (it will serialize as null, and the server default applies at inference time). The model is not accepted here.

Validation

Validation runs before the call is billed, so a rejected create costs nothing:

Request

cURL
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 marketing language.",
    "description": "Terse, factual changelog voice.",
    "icon_emoji": "📝",
    "temperature": 0.4,
    "max_tokens": 800
  }'

Response

{
  "data": {
    "id": 412,
    "name": "Release Notes Writer",
    "description": "Terse, factual changelog voice.",
    "system_prompt": "You write concise, factual release notes. No marketing language.",
    "icon_emoji": "📝",
    "temperature": 0.4,
    "max_tokens": 800,
    "top_p": null,
    "frequency_penalty": null,
    "presence_penalty": null,
    "locked": false,
    "message_count": 0
  },
  "meta": { "request_id": "9c1e77aa-04f3-4b8d-a25c-6e19d0b74f82" }
}

Retrieve a persona

GET/api/v1/personas/{id}

Returns one persona by id. Requires personas:read. An id you do not own, or one that does not exist, returns 404 not_found — the two cases are intentionally indistinguishable. A non-numeric path segment also returns 404.

Path parameters

NameTypeRequiredDescription
id integer Yes The id of the persona, from a list or create response.

Request

cURL
curl https://exe.krallo.com/api/v1/personas/412 \
  -H "Authorization: Bearer krallo_live_..."

Response

{
  "data": {
    "id": 412,
    "name": "Release Notes Writer",
    "description": "Terse, factual changelog voice.",
    "system_prompt": "You write concise, factual release notes. No marketing language.",
    "icon_emoji": "📝",
    "temperature": 0.4,
    "max_tokens": 800,
    "top_p": null,
    "frequency_penalty": null,
    "presence_penalty": null,
    "locked": false,
    "message_count": 37
  },
  "meta": { "request_id": "7a1d0e4b-83c2-4f61-95a8-d34e6b0c9f17" }
}

Update a persona

PATCH/api/v1/personas/{id}

Applies a partial update and returns the full, updated persona. Requires personas:write. Send only the fields you want to change — any field you omit is left as-is. Responds 200 on success. PUT is accepted and behaves identically to PATCH (it is still a partial update, not a full replace).

Locked personas cannot be edited. If the target persona has locked: true, the update is refused with 409 persona_locked and nothing changes. Ownership is checked first, so a persona you do not own returns 404 not_found even if it is locked.

Path parameters

NameTypeRequiredDescription
id integer Yes The id of the persona to update.

Body parameters

All of the create fields are accepted, and all are optional here. Each is validated with the same rules and bounds as on create; a wrong-typed or out-of-range value returns 400 invalid_request and the persona is unchanged. The model is not accepted. An empty body is valid and is a no-op that returns the persona unchanged.

NameTypeRequiredDescription
namestringNoNew display name. Non-blank, at most 120 characters.
system_promptstringNoNew instruction text. Non-blank, at most 12,000 characters.
descriptionstringNoNew note. At most 600 characters.
icon_emojistringNoNew emoji. At most 16 characters.
temperaturenumberNo0–2.
max_tokensintegerNo1–32000.
top_pnumberNo0–1.
frequency_penaltynumberNo-2–2.
presence_penaltynumberNo-2–2.

Request

cURL
curl -X PATCH https://exe.krallo.com/api/v1/personas/412 \
  -H "Authorization: Bearer krallo_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "temperature": 0.2, "max_tokens": 1200 }'

Response

{
  "data": {
    "id": 412,
    "name": "Release Notes Writer",
    "description": "Terse, factual changelog voice.",
    "system_prompt": "You write concise, factual release notes. No marketing language.",
    "icon_emoji": "📝",
    "temperature": 0.2,
    "max_tokens": 1200,
    "top_p": null,
    "frequency_penalty": null,
    "presence_penalty": null,
    "locked": false,
    "message_count": 37
  },
  "meta": { "request_id": "c22f61a9-d04e-4b28-8c75-3a91e5f0d6b2" }
}

Delete a persona

DELETE/api/v1/personas/{id}

Permanently deletes one persona by id. Requires personas:write. Deletion is owner-scoped: an id you do not own, or one that does not exist, returns 404 not_found. A locked persona cannot be deleted and returns 409 persona_locked.

Path parameters

NameTypeRequiredDescription
id integer Yes The id of the persona to delete. A non-numeric path segment returns 404 not_found.

Request

cURL
curl -X DELETE https://exe.krallo.com/api/v1/personas/412 \
  -H "Authorization: Bearer krallo_live_..."

Response

{
  "data": { "id": 412, "deleted": true },
  "meta": { "request_id": "e70b2c88-15d9-4a37-b642-90c1f5e8a3d4" }
}

Billing & rate limits

Every persona call — list, retrieve, create, update, and delete — is a flat metered request. Calls first draw down your app’s free daily allowance; once that is exhausted, and if wallet billing is enabled, each call debits a small, fixed number of credit units from the owner’s balance. If the wallet cannot cover a billable call, the request returns 402 insufficient_credits with required_units and balance in the error body, and nothing is changed. Validation failures on create/update are checked before billing, and refused or errored calls are refunded and do not burn quota. See Billing for the full model.

Requests are rate-limited per key by your app’s tier. Every authenticated response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers (pre-auth 401/403 responses do not); exceeding the limit returns 429 with a Retry-After header. See Rate limits.

Errors

Persona endpoints can return the following errors, in addition to the standard bearer-auth failures:

StatusCodeWhen
400invalid_requestMissing/blank name or system_prompt on create; a wrong-typed sampling field; an out-of-range number; or an over-length string.
401unauthorizedNo bearer token was supplied.
401invalid_token · token_revoked · token_expiredThe key is unknown, revoked, or past its expiry.
402insufficient_creditsOver the free quota and the wallet cannot cover the billable call.
403insufficient_scopeThe key lacks personas:read (reads) or personas:write (create/update/delete). The needed scope is echoed in required_scope.
403app_disabled · owner_suspendedThe owning app is disabled, or the account is suspended.
404not_foundThe persona does not exist or is not owned by your account, a non-numeric id, or the developer platform is disabled.
405method_not_allowedAn unsupported method for the path.
409persona_lockedAn update or delete targeted a locked persona.
429rate_limitedThe per-key rate limit was exceeded. This response uses a bare { "error": "rate_limited", "hint", "retryAfterSeconds" } shape (not the standard envelope) and carries a Retry-After header.
500internal_errorAn unexpected server-side failure; the call is refunded.

Apart from the 429 noted above, all errors share the native envelope shape. For status semantics and retry guidance, see Errors. Related reading: Memories, Chat completions, and Authentication.