Toutes les routes retournent du JSON. Les erreurs suivent le format standard :
{ "error": { "code": "ERROR_CODE", "message": "Description lisible" } }Authentification : cookie HTTP-only géré par Better Auth, vérifié par middleware sur toutes les routes /api/* (sauf /api/auth/*).
Créé automatiquement par Better Auth.
// Request
{ name: string, email: string, password: string }
// Response 200
{ user: { id: string, name: string, email: string }, session: { token: string } }// Request
{ email: string, password: string }
// Response 200
{ user: { id: string, name: string, email: string }, session: { token: string } }// Response 200
{ success: true }Vérifie si l'onboarding a été complété (au moins un user + providers avec llm et embedding).
// Response 200
{ completed: boolean, hasAdmin: boolean, hasLlm: boolean, hasEmbedding: boolean }// Response 200
{
id: string
email: string
firstName: string
lastName: string
pseudonym: string
language: 'fr' | 'en'
role: 'admin' | 'user'
avatarUrl: string | null
}// Request (tous les champs optionnels)
{
firstName?: string
lastName?: string
pseudonym?: string
language?: 'fr' | 'en'
password?: { current: string, new: string }
}
// Response 200
{ ...same as GET /api/me }Upload multipart/form-data.
// Request: FormData avec champ "file"
// Response 200
{ avatarUrl: string }// Response 200
{
providers: Array<{
id: string
name: string
type: 'anthropic' | 'openai' | 'gemini' | 'voyage_ai'
capabilities: ('llm' | 'embedding' | 'image')[]
isValid: boolean
createdAt: number
}>
}// Request
{
name: string
type: 'anthropic' | 'openai' | 'gemini' | 'voyage_ai'
config: { apiKey: string, baseUrl?: string }
}
// Response 201
{ provider: { id: string, name: string, type: string, capabilities: string[], isValid: boolean } }Le serveur teste la connexion et détecte les capacités avant de retourner.
// Request (tous optionnels)
{ name?: string, config?: { apiKey?: string, baseUrl?: string } }
// Response 200
{ provider: { ...same shape } }// Response 200
{ success: true }
// Error 409 si c'est le dernier provider couvrant une capacité requise (llm ou embedding)
{ error: { code: "PROVIDER_REQUIRED", message: "..." } }Teste la connexion au provider.
// Response 200
{ valid: boolean, capabilities: string[], error?: string }Liste tous les modèles disponibles a travers tous les providers configurés.
// Response 200
{
models: Array<{
id: string // ex: 'claude-sonnet-4-20250514'
name: string // ex: 'Claude Sonnet 4'
providerId: string
providerType: string
capability: 'llm' | 'embedding'
}>
}// Response 200
{
kins: Array<{
id: string
name: string
role: string
avatarUrl: string | null
model: string
createdAt: number
// Pas de character/expertise ici (trop volumineux pour la liste)
}>
}// Response 200
{
id: string
name: string
role: string
avatarUrl: string | null
character: string
expertise: string
model: string
workspacePath: string
mcpServers: Array<{ id: string, name: string }>
queueSize: number // nombre de messages en attente
isProcessing: boolean // en train de traiter un message
createdAt: number
}// Request
{
name: string
role: string
character: string
expertise: string
model: string
mcpServerIds?: string[]
avatar?: 'upload' | 'generate' | 'prompt'
avatarPrompt?: string // si avatar === 'prompt'
}
// Si avatar === 'upload', utiliser POST /api/kins/:id/avatar après création
// Response 201
{ kin: { ...same as GET /api/kins/:id } }// Request (tous optionnels)
{
name?: string
role?: string
character?: string
expertise?: string
model?: string
mcpServerIds?: string[]
}
// Response 200
{ kin: { ...same shape } }// Response 200
{ success: true }Upload ou génération d'avatar.
// Mode upload : FormData avec champ "file"
// Mode generate : { mode: 'generate' }
// Mode prompt : { mode: 'prompt', prompt: string }
// Response 200
{ avatarUrl: string }Reconstruit et retourne le contexte LLM complet tel qu'il serait envoyé au modèle. Utile pour le debugging et la transparence. Accepte des query params optionnels pour les tâches et sessions rapides.
// Query params optionnels :
// ?taskId={string} — contexte d'une tâche spécifique
// ?sessionId={string} — contexte d'une session rapide
// Response 200
{
systemPrompt: string // Prompt système complet (avec outils en annexe)
compactingSummary: string | null // Résumé de compacting (null si pas de compacting)
rawPayload: {
system: string
messages: Array<{
role: string
content: string | null
hasToolCalls: boolean
createdAt: number | null
}>
tools: Array<{
name: string
description: string
parameters: Record<string, unknown> | null
}>
}
tokenEstimate: {
systemPrompt: number
summary: number
messages: number
tools: number
total: number
}
contextWindow: number // Taille max du contexte du modèle (en tokens)
messageCount: number
generatedAt: number
}Envoie un message a un Kin. Déclenche le traitement et le streaming SSE de la réponse.
// Request
{
content: string
files?: string[] // IDs de fichiers déjà uploadés
}
// Response 202
{ messageId: string, queuePosition: number }La réponse du Kin arrive via SSE (pas dans cette response HTTP).
Historique paginé des messages.
// Query params : ?before={messageId}&limit={number, default 50}
// Response 200
{
messages: Array<{
id: string
role: 'user' | 'assistant' | 'system' | 'tool'
content: string
sourceType: 'user' | 'kin' | 'task' | 'cron' | 'system'
sourceId: string | null
sourceName: string | null // pseudonym, kin name, task name, cron name
isRedacted: boolean
files: Array<{ id: string, name: string, mimeType: string, url: string }>
createdAt: number
}>
hasMore: boolean
}Liste toutes les tâches en cours.
// Query params : ?status={pending|in_progress|completed|failed|cancelled}&kinId={string}
// Response 200
{
tasks: Array<{
id: string
parentKinId: string
parentKinName: string
description: string
status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled'
mode: 'await' | 'async'
depth: number
createdAt: number
updatedAt: number
}>
}Détail d'une tâche avec ses messages.
// Response 200
{
task: { ...same as list item + result: string | null, error: string | null }
messages: Array<{ ...same as message shape }>
}// Response 200
{ success: true }// Query params : ?kinId={string}
// Response 200
{
crons: Array<{
id: string
kinId: string
kinName: string
name: string
schedule: string
taskDescription: string
targetKinId: string | null
model: string | null
isActive: boolean
requiresApproval: boolean
lastTriggeredAt: number | null
createdAt: number
}>
}// Request
{
kinId: string
name: string
schedule: string
taskDescription: string
targetKinId?: string
model?: string
}
// Response 201
{ cron: { ...same shape } }// Request (tous optionnels)
{
name?: string
schedule?: string
taskDescription?: string
targetKinId?: string
model?: string
isActive?: boolean
}
// Response 200
{ cron: { ...same shape } }// Response 200
{ success: true }Approuve un cron créé par un Kin (qui nécessite validation).
// Response 200
{ cron: { ...same shape, requiresApproval: false, isActive: true } }// Response 200
{
servers: Array<{
id: string
name: string
command: string
args: string[]
env: Record<string, string> | null
createdAt: number
}>
}// Request
{ name: string, command: string, args?: string[], env?: Record<string, string> }
// Response 201
{ server: { ...same shape } }// Response 200
{ success: true }Liste les secrets (clés uniquement, jamais les valeurs).
// Response 200
{
secrets: Array<{
id: string
key: string
createdAt: number
updatedAt: number
}>
}// Request
{ key: string, value: string }
// Response 201
{ secret: { id: string, key: string, createdAt: number } }// Request
{ key?: string, value?: string }
// Response 200
{ secret: { id: string, key: string, updatedAt: number } }// Response 200
{ success: true }Upload multipart/form-data.
// Request: FormData avec champ "file" + "kinId"
// Response 201
{ file: { id: string, name: string, mimeType: string, size: number, url: string } }// Query params : ?category={fact|preference|decision|knowledge}&subject={string}&limit={number}
// Response 200
{
memories: Array<{
id: string
content: string
category: 'fact' | 'preference' | 'decision' | 'knowledge'
subject: string | null
sourceChannel: 'automatic' | 'explicit'
createdAt: number
updatedAt: number
}>
}// Response 200
{ success: true }Réinitialise le compacting (supprime le snapshot actif).
// Response 200
{ success: true }Liste les snapshots pour le rollback.
// Response 200
{
snapshots: Array<{
id: string
messagesUpToId: string
isActive: boolean
createdAt: number
}>
}// Request
{ snapshotId: string }
// Response 200
{ success: true }Routes d'administration pour les paramètres globaux de la plateforme (admin uniquement).
// Response 200
{ globalPrompt: string }// Request
{ globalPrompt: string }
// Response 200
{ globalPrompt: string }Endpoint legacy (extraction + embedding uniquement).
// Response 200
{ extractionModel: string | null, embeddingModel: string | null, extractionProviderId: string | null, embeddingProviderId: string | null }Retourne tous les modèles/services par défaut en un seul payload.
// Response 200
{
defaultLlmModel: string | null
defaultLlmProviderId: string | null
defaultImageModel: string | null
defaultImageProviderId: string | null
defaultCompactingModel: string | null
defaultCompactingProviderId: string | null
extractionModel: string | null
extractionProviderId: string | null
embeddingModel: string | null
embeddingProviderId: string | null
searchProviderId: string | null
}// Request
{ model: string | null, providerId?: string | null }
// Response 200
{ defaultLlmModel: string | null, defaultLlmProviderId: string | null }// Request
{ model: string | null, providerId?: string | null }
// Response 200
{ defaultImageModel: string | null, defaultImageProviderId: string | null }// Request
{ model: string | null, providerId?: string | null }
// Response 200
{ defaultCompactingModel: string | null, defaultCompactingProviderId: string | null }// Request
{ model: string | null, providerId?: string | null }
// Response 200
{ extractionModel: string | null, extractionProviderId: string | null }// Request
{ model: string, providerId?: string | null }
// Response 200
{ embeddingModel: string, embeddingProviderId: string | null }// Response 200
{ searchProviderId: string | null }// Request
{ searchProviderId: string | null }
// Response 200
{ searchProviderId: string | null }// Response 200
{ hubKinId: string | null, hubKinName: string | null, hubKinSlug: string | null }// Request
{ kinId: string | null }
// Response 200
{ hubKinId: string | null }Connexion SSE globale (une seule par client). Le serveur multiplex les événements de tous les Kins.
// Tokens LLM en streaming
{ event: 'chat:token', data: { kinId: string, token: string } }
// Réponse LLM terminée
{ event: 'chat:done', data: { kinId: string, messageId: string } }
// Nouveau message dans le chat (autres sources)
{ event: 'chat:message', data: { kinId: string, message: MessageShape } }
// Changement d'état d'une tâche
{ event: 'task:status', data: { taskId: string, kinId: string, status: string } }
// Tâche terminée
{ event: 'task:done', data: { taskId: string, kinId: string, result: string } }
// Exécution d'un cron
{ event: 'cron:triggered', data: { cronId: string, kinId: string, taskId: string } }
// Queue mise a jour
{ event: 'queue:update', data: { kinId: string, queueSize: number, isProcessing: boolean, processingStartedAt?: number } }
// Erreur sur un Kin
{ event: 'kin:error', data: { kinId: string, error: string } }Le SSE est global (pas par Kin). Le client filtre côté frontend par
kinIdpour n'afficher que les événements pertinents. Cela permet de mettre a jour la sidebar (badges, statuts) pour tous les Kins simultanément.