Backend Profile Management API Reference¶
REST API documentation for managing backend profiles (video processing configurations).
Base URL¶
All endpoints are under the /admin prefix.
Authentication¶
Currently no authentication required. Future versions will require API keys or OAuth tokens.
Common Headers¶
Error Responses¶
All endpoints return standard HTTP error responses:
Or for business-rule validation errors raised by ProfileValidator (e.g. duplicate profile name, invalid profile name characters, unknown embedding type, missing schema template):
{
"detail": {
"message": "Profile validation failed",
"errors": [
"Profile 'video_test' already exists for tenant 'acme_corp'",
"Invalid embedding type 'wrong_type'. Must be one of: ['multi_vector', 'single_vector']"
]
}
}
Or for request-schema validation errors (missing/malformed required field, caught by FastAPI/Pydantic before the route body runs):
{
"detail": [
{
"type": "missing",
"loc": ["body", "tenant_id"],
"msg": "Field required",
"input": {"profile_name": "test", "...": "..."}
}
]
}
Common status codes:
-
400: Bad request (business-rule validation error — e.g. duplicate profile name, invalid profile name, unknown embedding type, missing schema template) -
404: Resource not found -
409: Conflict — only returned by Delete Profile whendelete_schema=trueand another profile still references the same schema -
422: Unprocessable entity (request body missing a required field or failing Pydantic type validation, e.g.tenant_idomitted entirely) -
500: Internal server error
Endpoints¶
1. Create Profile¶
Create a new backend profile for a tenant.
Endpoint: POST /admin/profiles
Request Body:
{
"profile_name": "string (required)",
"tenant_id": "string (required)",
"type": "string (optional, default: 'video')",
"schema_name": "string (required)",
"embedding_model": "string (required)",
"embedding_type": "string (required, enum: multi_vector|single_vector)",
"description": "string (optional, default: '')",
"strategies": {
"segmentation": {
"class": "string",
"params": {}
},
"embedding": {
"class": "string",
"params": {}
}
},
"pipeline_config": {
"frame_extraction": {
"fps": 1,
"max_frames": 100
}
},
"model_specific": {
"quantization": "int8"
},
"schema_config": {
"embedding_dim": 128,
"num_patches": 1024
},
"model_loader": "string (required for embedded types, enum: colbert|colpali|colqwen|xclip)",
"process_type": "string (optional, enum: direct_video|frame_based|video_chunks)",
"extra_config": {
"inference_services": {"embedding": "vllm_colpali"}
},
"deploy_schema": "boolean (optional, default: false)"
}
Example Request:
curl -X POST http://localhost:8000/admin/profiles \
-H "Content-Type: application/json" \
-d '{
"profile_name": "video_colpali_mv_frame",
"tenant_id": "acme_corp",
"type": "video",
"schema_name": "video_colpali_smol500_mv_frame",
"embedding_model": "TomoroAI/tomoro-colqwen3-embed-4b",
"embedding_type": "multi_vector",
"model_loader": "colpali",
"extra_config": {"inference_services": {"embedding": "vllm_colpali"}},
"description": "ColPali model with frame-based embedding for video search",
"strategies": {
"segmentation": {
"class": "FrameSegmentationStrategy",
"params": {"fps": 0.5, "max_frames": 100}
},
"embedding": {
"class": "MultiVectorEmbeddingStrategy",
"params": {}
}
},
"pipeline_config": {
"frame_extraction": {
"fps": 1,
"max_frames": 100
}
}
}'
Response: 201 Created
{
"profile_name": "video_colpali_mv_frame",
"tenant_id": "acme_corp",
"schema_deployed": false,
"tenant_schema_name": null,
"schema_deploy_error": null,
"created_at": "2024-01-15T10:00:00.000Z",
"version": 1
}
With deploy_schema, a deploy that fails after the profile is stored still answers 201: schema_deployed is false and schema_deploy_error names the schema and the failure, for a deploy from the profile.
Validation Rules:
profile_name: Must be unique within tenant, max 100 chars, alphanumeric + underscore + hyphen onlytenant_id: Required, non-empty — identifies the tenant owning the profiletype: Optional (defaults to "video"), must be a type of a shipped profile inconfigs/config.jsonbackend.profiles("video", "image", "audio", "document", "code", "wiki")schema_name: Must have a matching template file{schema_name}_schema.jsonin the configured schema templates directory (defaults toconfigs/schemas/); the template must contain top-levelnameanddocument.fieldsembedding_model: Formatorg/modelormodel-nameembedding_type: Must bemulti_vectororsingle_vectorstrategies: Optional (defaults to empty dict); each entry must be an object with aclasskey naming an importable strategy classpipeline_config: Optional (defaults to empty dict), must be valid JSON objectmodel_specific: Optional (defaults to null), must be valid JSON objectschema_config: Optional (defaults to empty dict); if it includesembedding_dim, the value must be an integer between 1 and 100000model_loader: The loader ingestion embeds with:colbert,colpali,colqwenorxclip(EMBEDDING_MODEL_LOADERS). Required when every shipped profile of the profile'stypenames one (today every type butwiki); a profile without it could be deployed but not ingested intoprocess_type: Optional (defaults to null, inferred by ingestion); one ofdirect_video,frame_based,video_chunks(PROCESS_TYPES)extra_config: Optional (defaults to empty dict); further profile keys stored beside the named fields, such asinference_services,model_config,result_granularityorsemantic_model. A key named like a profile field is refuseddeploy_schema: Optional (defaults to false), boolean flag
2. List Profile Templates¶
The shipped profiles a tenant's new profile can start from, each with its whole configuration as ingestion reads it. A shipped name the tenant has created its own profile under is left out.
Endpoint: GET /admin/profile-templates
Query Parameters:
tenant_id(required): Tenant identifier
Response: 200 OK
{
"tenant_id": "acme_corp",
"templates": [
{
"profile_name": "document_text_semantic",
"config": {
"type": "document",
"schema_name": "document_text",
"embedding_model": "lightonai/LateOn",
"embedding_type": "multi_vector",
"model_loader": "colbert",
"inference_services": {"embedding": "colbert_pylate"},
"result_granularity": "source",
"...": "..."
}
}
],
"profile_types": ["video", "image", "audio", "document", "code", "wiki"],
"embedding_types": ["multi_vector", "single_vector"],
"model_loaders": ["colbert", "colpali", "colqwen", "xclip"],
"process_types": ["direct_video", "frame_based", "video_chunks"]
}
profile_types, embedding_types, model_loaders and process_types are the values a new profile's choice fields take.
A template becomes a create request by moving its named keys to the fields of the same name and every other key into extra_config.
Error Response: 500 with error: "profile_templates_failed" when the config store cannot be read.
3. List Profiles¶
Get all backend profiles for a tenant.
Endpoint: GET /admin/profiles
Query Parameters:
tenant_id(required): Tenant identifier
Example Request:
Response: 200 OK
{
"profiles": [
{
"profile_name": "video_colpali_mv_frame",
"type": "video",
"schema_name": "video_colpali_smol500_mv_frame",
"embedding_model": "TomoroAI/tomoro-colqwen3-embed-4b",
"description": "ColPali model with frame-based embedding",
"schema_deployed": true,
"created_at": "2024-01-15T10:00:00.000Z"
},
{
"profile_name": "video_xclip_global",
"type": "video",
"schema_name": "video_xclip_sv_chunk_6s",
"embedding_model": "microsoft/xclip-large-patch14",
"description": "X-CLIP global embeddings",
"schema_deployed": false,
"created_at": "2024-01-15T10:00:00.000Z"
}
],
"total_count": 2,
"tenant_id": "acme_corp"
}
Notes:
-
Returns empty array if no profiles exist for tenant
-
Profiles are tenant-isolated (cannot see other tenants' profiles)
-
The list reads the tenant's stored profiles, so a profile created, updated or deleted through any runtime worker or replica is listed as it is at once
-
created_atin this list response is NOT a persisted per-profile creation timestamp — the config store doesn't track it, so the route fills it in as the current time of the list request. Every profile in a given response therefore shows the samecreated_at. Use Get Profile for a real per-profile version/timestamp derived from the config store's version history.
4. Get Profile¶
Get a specific backend profile by name.
Endpoint: GET /admin/profiles/{profile_name}
Path Parameters:
profile_name: Profile identifier
Query Parameters:
tenant_id(required): Tenant identifier
Example Request:
Response: 200 OK
{
"profile_name": "video_colpali_mv_frame",
"tenant_id": "acme_corp",
"type": "video",
"schema_name": "video_colpali_smol500_mv_frame",
"embedding_model": "TomoroAI/tomoro-colqwen3-embed-4b",
"embedding_type": "multi_vector",
"description": "ColPali model with frame-based embedding for video search",
"strategies": {
"segmentation": {
"class": "FrameSegmentationStrategy",
"params": {"fps": 0.5, "max_frames": 100}
},
"embedding": {
"class": "MultiVectorEmbeddingStrategy",
"params": {}
}
},
"pipeline_config": {
"frame_extraction": {
"fps": 1,
"max_frames": 100
}
},
"schema_config": {},
"model_specific": null,
"model_loader": "colpali",
"process_type": null,
"extra_config": {"inference_services": {"embedding": "vllm_colpali"}},
"schema_deployed": false,
"tenant_schema_name": null,
"created_at": "2024-01-15T10:00:00.000Z",
"version": 1
}
Error Response: 404 Not Found
Notes:
- The profile,
versionandcreated_atcome from one read of the tenant's stored backend config, so a write through any runtime worker or replica is answered at once, and the content andversionare always from the same write
5. Update Profile¶
Update mutable fields of an existing profile.
Endpoint: PUT /admin/profiles/{profile_name}
Path Parameters:
profile_name: Profile identifier
Request Body:
{
"tenant_id": "string (required)",
"description": "string (optional)",
"strategies": "object (optional)",
"pipeline_config": "object (optional)",
"model_specific": "object (optional)"
}
Mutable Fields:
-
description -
strategies(Dict[str, Any]) -
pipeline_config(Dict[str, Any]) -
model_specific
Immutable Fields (cannot be updated, create new profile instead):
-
type -
schema_name -
embedding_model -
schema_config -
model_loader
ProfileUpdateRequest only declares tenant_id, description, strategies, pipeline_config, and model_specific, so any immutable field name sent in the request body (e.g. schema_name) is silently dropped by Pydantic before the route body runs — it never reaches the update logic. If that leaves no recognized fields to change, the request fails with 400 Bad Request: "No fields to update provided" (see below) rather than a "cannot update immutable field" error.
Example Request:
curl -X PUT http://localhost:8000/admin/profiles/video_colpali_mv_frame \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "acme_corp",
"description": "Updated: ColPali with optimized frame extraction",
"pipeline_config": {
"frame_extraction": {
"fps": 0.5,
"max_frames": 50
}
}
}'
Response: 200 OK
{
"profile_name": "video_colpali_mv_frame",
"tenant_id": "acme_corp",
"updated_fields": ["description", "pipeline_config"],
"version": 2
}
Error Response: 400 Bad Request (no mutable fields provided, e.g. body only contained an immutable field name)
If strategies is one of the provided mutable fields, its value is re-validated with the same strategy-class checks used at creation time; a bad strategies block returns 400 with {"message": "Invalid update fields", "errors": [...]}.
Concurrency:
-
Updates are serialized via a single in-process lock on the shared
ConfigManagerinstance — it guards backend-profile reads/writes for every tenant in that runtime process, not just the tenant being updated; it does not span multiple runtime processes -
The config store versions every write — each successful update creates a new version and
versionin the response is the resulting version number
6. Delete Profile¶
Delete a backend profile and optionally its backend schema.
Endpoint: DELETE /admin/profiles/{profile_name}
Path Parameters:
profile_name: Profile identifier
Query Parameters:
tenant_id(required): Tenant identifierdelete_schema(optional, default=false): Also delete backend schema
Example Request (profile only):
Example Request (profile + schema):
curl -X DELETE "http://localhost:8000/admin/profiles/video_colpali_mv_frame?tenant_id=acme_corp&delete_schema=true"
Response: 200 OK
{
"profile_name": "video_colpali_mv_frame",
"tenant_id": "acme_corp",
"schema_deleted": true,
"deleted_at": "2024-01-15T10:30:00.000Z"
}
Error Response: 404 Not Found
Error Response: 409 Conflict (only when delete_schema=true and another profile in the same tenant still references the schema)
{
"detail": "Cannot delete schema 'video_colpali_smol500_mv_frame': other profiles using it: ['video_colpali_other_profile']"
}
Error Response: 500 (only when delete_schema=true and the redeploy would also drop deployed schemas the schema registry does not know — e.g. a schema deployed without registration). The delete is refused instead of silently destroying the sibling schemas; register them or remove them explicitly first (delete_tenant_schemas / POST /admin/reconcile-orphans), then retry.
{
"detail": "Refusing to delete 'video_colpali_smol500_mv_frame_acme_acme': redeploying without it would also drop ['knowledge_graph_acme_acme'] — deployed schemas the registry does not know and cannot reconstruct. Register them or remove them explicitly first (delete_tenant_schemas / POST /admin/reconcile-orphans)."
}
Notes:
-
Deletion is permanent - no undo
-
If
delete_schema=truebut schema doesn't exist, operation still succeeds -
Cannot delete other tenants' profiles
7. Deploy Schema¶
Deploy backend schema for a profile to the configured backend.
Endpoint: POST /admin/profiles/{profile_name}/deploy
Path Parameters:
profile_name: Profile identifier
Request Body:
Parameters:
force: If true, redeploy even if already deployed
Example Request:
curl -X POST http://localhost:8000/admin/profiles/video_colpali_mv_frame/deploy \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "acme_corp",
"force": false
}'
Response: 200 OK
{
"profile_name": "video_colpali_mv_frame",
"tenant_id": "acme_corp",
"schema_name": "video_colpali_smol500_mv_frame",
"tenant_schema_name": "video_colpali_smol500_mv_frame_acme_corp_acme_corp",
"deployment_status": "success",
"deployed_at": "2024-01-15T10:30:00.000Z"
}
Error Response: 200 OK (with failed status)
{
"profile_name": "video_colpali_mv_frame",
"tenant_id": "acme_corp",
"schema_name": "video_colpali_smol500_mv_frame",
"tenant_schema_name": "",
"deployment_status": "failed",
"deployed_at": "2024-01-15T10:30:00.000Z",
"error_message": "Connection to Vespa refused"
}
Deployment Process:
- Check if schema already exists (skip if exists and force=false)
- Call backend.schema_registry.deploy_schema() with tenant_id, base_schema_name, optional config, and optional force parameter
- Generate tenant-specific schema name via
backend.get_tenant_schema_name(tenant_id, base_schema_name): the tenant_id is first canonicalized toorg:tenantform (a bare tenant likeacme_corpcanonicalizes toacme_corp:acme_corp; an explicitorg:tenantvalue likeacme:prodis used as-is), then the colon is replaced with an underscore and appended — sotenant_id="acme_corp"yields{base_schema_name}_acme_corp_acme_corp, andtenant_id="acme:prod"yields{base_schema_name}_acme_prod - Return deployment status ("success", "failed", or "already_deployed")
Prerequisites:
- Profile must exist
-
The profile resolves from the tenant's stored profiles first, otherwise from the tenant's merged catalog of shipped (config.json) profiles, which
GET /admin/profilesandGET /admin/profiles/{profile_name}do not show -
Schema template must exist in configured schema directory
-
Backend must be accessible
-
System config must have valid
backend_url
Request/Response Schemas¶
ProfileCreateRequest¶
{
profile_name: string, // Required, unique within tenant
tenant_id: string, // Required: tenant identifier for isolation
type?: string, // Optional (default: "video")
schema_name: string, // Required, must exist in schema dir
embedding_model: string, // Required (e.g., "TomoroAI/tomoro-colqwen3-embed-4b")
embedding_type: "multi_vector" | "single_vector", // Required
description?: string, // Optional (default: "")
strategies?: object, // Optional (default: {}, Dict[str, Any])
pipeline_config?: object, // Optional (default: {}, Dict[str, Any])
model_specific?: object, // Optional (default: null, Dict[str, Any])
schema_config?: object, // Optional (default: {}, Dict[str, Any])
model_loader?: string, // colbert | colpali | colqwen | xclip; required for embedded types
process_type?: string | null, // direct_video | frame_based | video_chunks (default: null)
extra_config?: object, // Optional (default: {}), keys stored beside the fields
deploy_schema?: boolean // Optional (default: false)
}
ProfileSummary¶
{
profile_name: string,
type: string,
description: string,
schema_name: string,
embedding_model: string,
schema_deployed: boolean,
created_at: string // ISO 8601 timestamp
}
ProfileListResponse¶
ProfileDetail¶
{
profile_name: string,
tenant_id: string,
type: string,
schema_name: string,
embedding_model: string,
embedding_type: string,
description: string,
strategies: object, // Dict[str, Any]
pipeline_config: object, // Dict[str, Any]
schema_config: object, // Dict[str, Any]
model_specific: object | null,
model_loader: string,
process_type: string | null,
extra_config: object, // Dict[str, Any]
schema_deployed: boolean,
tenant_schema_name: string | null,
created_at: string, // ISO 8601 timestamp
version: number
}
ProfileUpdateRequest¶
{
tenant_id: string, // Required
description?: string, // Optional
strategies?: object, // Optional (Dict[str, Any])
pipeline_config?: object, // Optional (Dict[str, Any])
model_specific?: object // Optional (Dict[str, Any])
}
ProfileUpdateResponse¶
{
profile_name: string,
tenant_id: string,
updated_fields: string[], // Fields that were changed
version: number // Incremented version
}
ProfileDeleteResponse¶
{
profile_name: string,
tenant_id: string,
schema_deleted: boolean,
deleted_at: string // ISO 8601 timestamp
}
SchemaDeploymentRequest¶
SchemaDeploymentResponse¶
{
profile_name: string,
tenant_id: string,
schema_name: string,
tenant_schema_name: string,
deployment_status: string, // "success" | "failed" | "already_deployed"
deployed_at: string, // ISO 8601 timestamp
error_message?: string // Only present if deployment_status is "failed"
}
Complete Workflow Example¶
1. Create a new profile¶
curl -X POST http://localhost:8000/admin/profiles \
-H "Content-Type: application/json" \
-d '{
"profile_name": "video_test_profile",
"tenant_id": "test_tenant",
"type": "video",
"schema_name": "video_colpali_smol500_mv_frame",
"embedding_model": "TomoroAI/tomoro-colqwen3-embed-4b",
"embedding_type": "multi_vector",
"description": "Test profile for development"
}'
2. Deploy the schema¶
curl -X POST http://localhost:8000/admin/profiles/video_test_profile/deploy \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "test_tenant",
"force": false
}'
3. Check deployment status¶
4. Update pipeline configuration¶
curl -X PUT http://localhost:8000/admin/profiles/video_test_profile \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "test_tenant",
"pipeline_config": {
"frame_extraction": {
"fps": 2,
"max_frames": 200
}
}
}'
5. List all profiles for tenant¶
6. Delete profile and schema¶
curl -X DELETE "http://localhost:8000/admin/profiles/video_test_profile?tenant_id=test_tenant&delete_schema=true"
Rate Limiting¶
Currently no rate limiting. Future versions will implement:
-
Per-tenant request limits
-
Burst protection
-
Deployment throttling
Versioning¶
API version: v1 (implicit, no version prefix required)
Breaking changes will be introduced in new API versions (/v2/admin/profiles).
Client Libraries¶
Python¶
import requests
class ProfileClient:
def __init__(self, base_url: str = "http://localhost:8000"):
self.base_url = base_url
self.session = requests.Session()
self.session.headers.update({"Content-Type": "application/json"})
def create_profile(self, profile: dict) -> dict:
response = self.session.post(
f"{self.base_url}/admin/profiles",
json=profile,
timeout=30.0
)
response.raise_for_status()
return response.json()
def list_profiles(self, tenant_id: str) -> list:
response = self.session.get(
f"{self.base_url}/admin/profiles",
params={"tenant_id": tenant_id},
timeout=30.0
)
response.raise_for_status()
return response.json()["profiles"]
def deploy_schema(self, profile_name: str, tenant_id: str, force: bool = False) -> dict:
response = self.session.post(
f"{self.base_url}/admin/profiles/{profile_name}/deploy",
json={"tenant_id": tenant_id, "force": force},
timeout=30.0
)
response.raise_for_status()
return response.json()
# Usage
client = ProfileClient()
profile = client.create_profile({
"profile_name": "video_test",
"tenant_id": "my_tenant",
"type": "video",
"schema_name": "video_colpali_smol500_mv_frame",
"embedding_model": "TomoroAI/tomoro-colqwen3-embed-4b",
"embedding_type": "multi_vector"
})
JavaScript¶
class ProfileClient {
constructor(baseUrl = "http://localhost:8000") {
this.baseUrl = baseUrl;
}
async createProfile(profile) {
const response = await fetch(`${this.baseUrl}/admin/profiles`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(profile)
});
if (!response.ok) throw new Error(await response.text());
return response.json();
}
async listProfiles(tenantId) {
const response = await fetch(
`${this.baseUrl}/admin/profiles?tenant_id=${tenantId}`
);
if (!response.ok) throw new Error(await response.text());
const data = await response.json();
return data.profiles;
}
async deploySchema(profileName, tenantId, force = false) {
const response = await fetch(
`${this.baseUrl}/admin/profiles/${profileName}/deploy`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tenant_id: tenantId, force })
}
);
if (!response.ok) throw new Error(await response.text());
return response.json();
}
}
// Usage
const client = new ProfileClient();
const profile = await client.createProfile({
profile_name: "video_test",
tenant_id: "my_tenant",
type: "video",
schema_name: "video_colpali_smol500_mv_frame",
embedding_model: "TomoroAI/tomoro-colqwen3-embed-4b",
embedding_type: "multi_vector"
});
Next Steps¶
- Backend Profile Management - UI guide
- Dynamic Profiles Architecture - System design