API reference
All endpoints are mounted at /v1/modules/scaipersona/ and authenticate with the standard ScaiGrid bearer token (API key or JWT). Responses use ScaiGrid's standard envelope ({ "data": ... } for success, { "error": ... } for failures). Once a persona is published, invocation happens through POST /v1/inference/chat — not through any endpoint listed below.
Personas#
GET /personas#
List personas in the caller's tenant. Cursor-paginated.
1 2 | |
Query parameters: limit, cursor, order (standard pagination).
POST /personas#
Create a persona.
| Field | Required | Notes |
|---|---|---|
name |
yes | Human-readable name. |
slug |
no | URL-safe, unique within tenant. Auto-derived from name if omitted. |
model_slug |
yes | Slug of the underlying frontend model to wrap. |
system_prompt |
no | Default "". Baked into the published frontend model. |
rag_enabled |
no | Default false. |
rag_strategy |
no | single_step (default), multi_step, agentic. |
rag_top_k |
no | 1-50, default 5. |
rag_min_score |
no | Float, optional. |
rag_context_template |
no | Template string with $context placeholder. |
rag_status_messages |
no | Map of status keys to template strings (used when status_messages_mode: "custom"). |
status_messages_mode |
no | none, standard (default), custom. |
default_params |
no | Arbitrary JSON merged into inference requests (temperature, max_tokens, etc.). |
status |
no | draft (default), active, archived. |
metadata |
no | Arbitrary JSON; not interpreted. |
Returns 201 Created with the new persona.
GET /personas/{persona_id}#
Fetch one persona's full config. Returns 404 PERSONA_NOT_FOUND if the persona doesn't exist or belongs to another tenant.
PUT /personas/{persona_id}#
Replace any subset of editable fields. Any non-null field in the body is applied; null fields are ignored. If the persona is currently published, the published frontend model is synced on save — display_name, system_prompt_template, avatar_url, default_params, and metadata are all updated in place.
DELETE /personas/{persona_id}#
Hard-delete the persona. If the persona is published, it's unpublished first (the frontend model is removed). Returns 204 No Content. Sources cascade-delete.
Publishing#
POST /personas/{persona_id}/publish#
Materialise the persona as a frontend model with slug tenant/{tenant_slug}/{persona_slug}.
Body (optional):
1 | |
The publish call:
- Creates a new
FrontendModelrow inheriting modality, capabilities, context window, and pricing from the underlying model. - Bakes the persona's
system_promptinto the frontend model'ssystem_prompt_template. - Copies the underlying model's backend links so dispatch resolves identically.
- Stores
{persona_id, rag_enabled}in the frontend model's metadata. - If
group_idsis present, adds the new model to those groups. Groups outside the caller's scope (global / cross-partner / cross-tenant) are silently skipped unless the caller is super-admin.
Idempotent: calling publish on an already-published persona returns the current state without error.
Returns 200 OK with the persona (now published: true, published_model_id populated).
POST /personas/{persona_id}/unpublish#
Remove the persona's frontend model. The model row is deleted, its memberships in any model groups are dropped, and the persona row is set to published: false. Returns 200 OK with the updated persona. Returns 409 PERSONA_NOT_PUBLISHED if the persona wasn't published.
Sources#
GET /personas/{persona_id}/sources#
List sources attached to a persona, in creation order.
POST /personas/{persona_id}/sources#
Attach a knowledge source.
| Field | Required | Notes |
|---|---|---|
source_type |
yes | collection (ScaiMatrix) or scaidrive (ScaiDrive share). |
source_id |
yes | Collection id or share id. |
source_name |
no | Display label used in RAG context formatting. |
weight |
no | 0.0-10.0, default 1.0. |
search_config |
no | Per-source opaque config (search_type for collections). |
Returns 201 Created. Returns 409 PERSONA_SOURCE_DUPLICATE if the same (source_type, source_id) is already attached.
PUT /personas/{persona_id}/sources/{source_id}#
Update a source's weight, search_config, or status (active/disabled). Disabled sources are skipped at retrieval time.
DELETE /personas/{persona_id}/sources/{source_id}#
Remove a source. Returns 204 No Content.
Avatars#
POST /personas/{persona_id}/avatar#
Upload an avatar image. Multipart form data: file (binary).
- Max size: 2 MB.
- Allowed types:
image/png,image/jpeg,image/gif,image/webp,image/svg+xml. - Magic-byte validation runs on non-SVG uploads; mismatched declared/actual types are rejected.
Returns { "data": { "avatar_url": "/v1/modules/scaipersona/personas/{id}/avatar" } }. If the persona is published, the published frontend model's avatar is synced on save.
Errors: INVALID_FILE_TYPE (400), FILE_TOO_LARGE (400), STORAGE_ERROR (502).
GET /personas/{persona_id}/avatar#
Serve the avatar image. Public — no authentication required. Cached for 1 hour. Returns 404 NO_AVATAR if no avatar was ever uploaded.
ScaiDrive integration#
GET /scaidrive/shares#
List ScaiDrive shares accessible to the calling user. The endpoint exchanges the caller's bearer token for a ScaiDrive-scoped token, then asks ScaiDrive what the user can see. Use this to populate UI pickers for "which share should this persona read from?".
Errors:
503 SCAIDRIVE_NOT_CONFIGURED— the module has noscaidrive_base_urlconfigured.502 SCAIDRIVE_TOKEN_EXCHANGE_FAILED— the caller's token couldn't be exchanged (e.g. an API key without ScaiDrive scope).
Invoking a published persona#
Once published, callers invoke the persona through the standard inference endpoint — there is no /test endpoint on this module.
1 2 3 4 5 6 7 | |
The persona enricher runs inside the inference pipeline — RAG retrieval and context injection happen automatically before dispatch.
Errors#
All endpoints return ScaiGrid's standard error envelope:
1 2 3 4 5 6 7 8 | |
ScaiPersona-specific codes:
| Code | HTTP | Meaning |
|---|---|---|
PERSONA_NOT_FOUND |
404 | Persona id doesn't exist, or it belongs to another tenant. |
PERSONA_SOURCE_NOT_FOUND |
404 | Source id doesn't exist for that persona. |
PERSONA_ALREADY_PUBLISHED |
409 | Publish called on an already-published persona (the route handles this as idempotent — current state returned). |
PERSONA_NOT_PUBLISHED |
409 | Unpublish called on a persona that isn't published. |
PERSONA_SLUG_CONFLICT |
409 | Another persona in the tenant already uses this slug. |
PERSONA_SOURCE_DUPLICATE |
409 | Same (source_type, source_id) already attached to the persona. |
PERSONA_MODEL_NOT_FOUND |
404 | The model_slug doesn't resolve to a frontend model — typically wrong slug, or the underlying model was deleted. |
INVALID_FILE_TYPE |
400 | Avatar upload had an unsupported MIME type or failed magic-byte validation. |
FILE_TOO_LARGE |
400 | Avatar upload exceeded 2 MB. |
NO_AVATAR |
404 | Avatar requested for a persona that has none. |
STORAGE_ERROR |
502 | S3 upload failed for an avatar. |
SCAIDRIVE_NOT_CONFIGURED |
503 | The module has no ScaiDrive base URL set in config. |
SCAIDRIVE_TOKEN_EXCHANGE_FAILED |
502 | Couldn't exchange the caller's token for a ScaiDrive token. |