Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

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.

bash
1
2
curl "$SCAIGRID_HOST/v1/modules/scaipersona/personas" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY"

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):

json
1
{ "group_ids": ["mg_xxx", "mg_yyy"] }

The publish call:

  • Creates a new FrontendModel row inheriting modality, capabilities, context window, and pricing from the underlying model.
  • Bakes the persona's system_prompt into the frontend model's system_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_ids is 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 no scaidrive_base_url configured.
  • 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.

bash
1
2
3
4
5
6
7
curl -X POST "$SCAIGRID_HOST/v1/inference/chat" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tenant/{tenant_slug}/{persona_slug}",
    "messages": [{"role": "user", "content": "..."}]
  }'

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:

json
1
2
3
4
5
6
7
8
{
  "error": {
    "code": "PERSONA_NOT_FOUND",
    "message": "Persona does not exist",
    "details": {}
  },
  "meta": { "request_id": "req_..." }
}

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.
Updated 2026-05-18 15:01:31 View source (.md) rev 12