API d'inférence IA
L’API d’inférence IA alimente le Schema designer du dashboard. Elle expose le même flux en trois étapes — profile, propose, render — sous forme d’endpoints que vous pouvez appeler depuis votre propre code. Utile lorsque vous souhaitez automatiser la génération de cubes sur plusieurs entrepôts, intégrer la création de cubes dans votre propre produit, ou intégrer Saiku Cloud à un flux d’onboarding en amont.
Tous les endpoints nécessitent une clé API Bearer. Voir Authentification pour les bases.
Le flux
Le Schema designer du dashboard est l’implémentation de référence canonique :
- Profilez une connexion ou un fichier téléversé. Nous
échantillonnons sa structure à moindre coût et renvoyons un
SchemaProfile. - Proposez un cube. Nous envoyons le profil + une intention
facultative en langage naturel à Claude, et renvoyons un
CubeProposalstructuré. - Rendez la proposition en XML de schema Mondrian. Le résultat est une chaîne prête à enregistrer comme schema dans votre espace de travail.
Vous pouvez appeler les étapes indépendamment — profilez une
fois et proposez plusieurs fois avec des intentions différentes,
ou sautez l’étape propose et fabriquez un CubeProposal à la
main pour le moteur de rendu.
POST /me/inference/profile/connection/{id}
Profilez une connexion d’entrepôt enregistrée. Lit
information_schema, échantillonne quelques lignes par colonne,
renvoie un profil structuré des tables et colonnes de l’entrepôt.
Paramètres de chemin :
id— l’ID de connexion depuisGET /me/connections.
Paramètres de requête facultatifs :
schema— restreint à un schéma de base de données spécifique (par ex.public,analytics). Par défaut, le schéma sur lequel la connexion a été enregistrée.maxTables— plafonne le nombre de tables profilées. Par défaut 20.tableTypes— liste séparée par virgules (TABLE,VIEW,MATERIALIZED VIEW). Par défautTABLEuniquement.
Réponse (200) :
{ "databaseProductName": "PostgreSQL", "databaseProductVersion": "16.6", "tables": [ { "schema": "public", "name": "fact_sales", "rowCount": 1245678, "columns": [ { "name": "sale_id", "type": "BIGINT", "nullable": false, "distinctApprox": 1245678, "sampleValues": [1, 2, 3, 4, 5] }, { "name": "customer_id", "type": "BIGINT", "nullable": false, "distinctApprox": 25000, "sampleValues": [101, 102, 103, 104, 105] } ] } ], "sampledAt": "2026-05-23T18:00:00Z", "sampleDurationMillis": 1832, "sampleCostUsd": 0.001}Coût : généralement inférieur à 0,05 $ par profil contre un entrepôt Postgres. La majeure partie du coût concerne les requêtes de métadonnées, pas les scans de données.
Modes de défaillance :
404 not_found— l’ID de connexion n’existe pas ou n’est pas visible pour votre tenant.502 warehouse_unreachable— nous n’avons pas pu nous connecter à l’entrepôt (identifiants incorrects, hôte indisponible, problème réseau).
GET /me/inference/profile/connection/{id}/sample
Récupère quelques lignes d’exemple d’une table. Mêmes auth/atteignabilité que l’endpoint de profil, portée plus restreinte.
Paramètres de chemin + requête :
id— ID de connexion.table— nom de la table (requis).schema— schéma de la base de données (par défaut, celui de la connexion).rows— nombre de lignes (par défaut 5, max 50).
Réponse (200) :
{ "schema": "public", "table": "fact_sales", "columns": ["sale_id", "customer_id", "amount", "sale_date"], "rows": [ [1, 101, "29.99", "2024-01-15"], [2, 102, "149.00", "2024-01-15"] ]}Utile pour montrer à l’utilisateur à quoi ressemblent réellement ses données avant qu’il ne s’engage sur une conception de cube.
POST /me/inference/profile/file
Profilez un fichier téléversé au lieu d’une table d’entrepôt. Même forme que le profileur de connexion, avec l’ID du fichier remplaçant l’ID de connexion.
Corps multipart :
file— la partie fichier..parquet,.csvou.json.tableTypes— par défautTABLE(même forme que le profileur de connexion, permet une extension future).
Réponse (200) : même forme SchemaProfile que le profileur
de connexion. Les fichiers apparaissent comme une seule entrée
tables[0].
httpfs de DuckDB lit le fichier colonne par colonne, donc un
fichier Parquet de plusieurs Go est profilé en quelques
secondes sans que nous ayons à le charger entièrement en
mémoire.
POST /me/inference/propose
Envoyez un profil à Claude et récupérez une proposition de cube.
Corps de la requête :
{ "profile": { /* SchemaProfile from the profile step */ }, "intent": "Sales facts joined to customer and product dimensions, sum of revenue, count of orders.", "factTable": { "schema": "public", "name": "fact_sales" }}profile(requis) — le JSON renvoyé par l’un ou l’autre des endpoints de profilage.intent(facultatif mais fortement recommandé) — description d’une ligne en langage naturel de ce que vous voulez. Sans cela, Claude a beaucoup moins de matière.factTable(facultatif) — épingle une table de faits spécifique. Sans cela, Claude en choisit une en se basant sur des heuristiques de nommage et de forme des colonnes.
Réponse (200) :
{ "traceId": "ac2ab729-7fc3-4c3a-8e36-7cd77be87267", "proposal": { "schemaName": "Sales Analytics", "cubes": [ { "name": "Sales", "factTableSchema": "public", "factTableName": "fact_sales", "measures": [ { "name": "Revenue", "column": "amount", "aggregator": "sum" }, { "name": "Orders", "column": "sale_id", "aggregator": "count" } ], "dimensions": [ { "name": "Customer", "foreignKey": "customer_id", "tableSchema": "public", "tableName": "dim_customer", "primaryKey": "customer_id", "levels": [ { "name": "Name", "column": "customer_name", "type": "String", "uniqueMembers": false } ] } ] } ] }, "mondrianXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Schema name=\"Sales Analytics\">\n <Cube name=\"Sales\">...</Cube>\n</Schema>", "validation": { "valid": true, "cubeCount": 1, "warnings": [] }, "summary": { /* condensed proposal — measures + dim names + join graph */ }, "detectorFindings": { /* heuristic insights about date hierarchies, etc */ }, "usage": { "model": "claude-sonnet-4-6", "inputTokens": 4321, "outputTokens": 892, "totalTokens": 5213, "estimatedCostUsd": 0.018, "isPricingExact": true }, "quota": { /* monthly LLM budget snapshot */ }}Point important : la réponse inclut déjà le mondrianXml
rendu pour le cas nominal. Vous n’avez pas besoin d’appeler
/render séparément, sauf si vous avez modifié la proposition
au préalable.
Le traceId vous permet de récupérer ultérieurement la
conversation LLM complète via
GET /me/inference/trace/{traceId}
— utile pour l’audit ou le débogage de propositions inattendues.
Modes de défaillance :
400 invalid_proposal— Claude a renvoyé une proposition structurellement invalide que notre validateur a rejetée. L’ID de trace est tout de même renvoyé.429 over_budget— votre tenant a dépassé son budget LLM mensuel. Accompagné d’un en-têteRetry-Afteret d’un champreset_atindiquant quand le budget se réinitialise.502 upstream_error— Claude a renvoyé une erreur réseau ou a refusé la requête.
POST /me/inference/render
Convertit un SchemaProposal en XML de schema Mondrian.
Corps de la requête — le JSON de proposition directement
(non enveloppé dans {proposal: …}). Utilisez le champ
proposal d’une réponse /propose précédente, avec vos
modifications locales appliquées :
{ "schemaName": "Sales Analytics", "cubes": [ { "name": "Sales", "factTableSchema": "public", "factTableName": "fact_sales", "measures": [ /* … */ ], "dimensions": [ /* … */ ] } ]}Réponse (200) :
{ "mondrianXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Schema name=\"Sales Analytics\">\n <Cube name=\"Sales\">...</Cube>\n</Schema>", "validation": { "valid": true, "cubeCount": 1, "warnings": [] }, "summary": { /* condensed view — measures + dim names + join graph */ }}Transformation pure — pas d’appel LLM, pas d’interaction entrepôt. Gratuit.
Vous pouvez appeler render plusieurs fois sur la même proposition au fur et à mesure que vous l’éditez — c’est exactement ce que fait le Schema designer dans la boucle d’édition du dashboard.
Modes de défaillance :
400 invalid_proposal— la proposition ne satisfait pas les contraintes du XML Mondrian (champ requis manquant, jointures contradictoires, etc.). Corps de réponse :{ error, kind, message }.400 empty_proposal— le corps de la requête était vide.
POST /me/inference/try-query
Exécute une requête MDX d’exemple sur un cube brouillon avant de l’enregistrer. Utile pour confirmer que les jointures tombent là où vous l’attendez.
Corps de la requête :
{ "proposal": { /* CubeProposal */ }, "connectionId": "uuid-of-saved-connection", "mdx": "SELECT { [Measures].[Revenue] } ON COLUMNS, { [Customer].[Name].MEMBERS } ON ROWS FROM [Sales]"}Réponse (200) :
{ "columns": ["Customer", "Revenue"], "rows": [ ["Acme Corp", "1234.56"], ["Beta Inc", "987.65"] ], "executionMillis": 234}Le cube est compilé en mémoire ; rien n’est enregistré. Utilisez cela dans votre propre boucle d’itération, comme le fait le Schema designer du dashboard.
GET /me/inference/trace/{traceId}
Récupère la conversation LLM d’un appel propose précédent.
Chaque réponse propose inclut un traceId ; passez-le ici pour
obtenir le prompt + la réponse complets.
Réponse (200) :
{ "traceId": "ac2ab729-7fc3-4c3a-8e36-7cd77be87267", "createdAt": "2026-05-23T18:00:00Z", "status": "success", "model": "claude-sonnet-4-6", "inputTokens": 4321, "outputTokens": 892, "estimatedCostUsd": 0.018, "promptText": "...full system + user prompt (often 20+ KB)...", "responseText": "...full LLM response, parsed and unparsed..."}Utile pour :
- Déboguer des propositions inattendues. Voir exactement ce que nous avons envoyé à Claude et ce qui est revenu.
- Piste d’audit. Les équipes de conformité souhaitent parfois disposer d’un enregistrement de ce que l’IA a généré pour examen humain.
- Itération. Comparer deux propositions consécutives pour voir ce que Claude a fait différemment.
Les traces sont conservées 30 jours, puis purgées. Limitées par RLS à votre tenant — vous ne pouvez récupérer que vos propres traces.
Exemple de bout en bout
Un script complet — profiler un entrepôt, proposer un cube, rendre le XML, l’enregistrer :
KEY="$SAIKU_API_KEY"CONN_ID="abc-123-def"
# 1. Profile (extract the `profile` field — propose wants the# profile object, not the whole response).PROFILE=$(curl -sS https://api.saiku.bi/me/inference/profile/connection/$CONN_ID \ -X POST -H "Authorization: Bearer $KEY" -d '{}' \ -H "Content-Type: application/json" | jq '.profile')
# 2. ProposePROPOSAL=$(curl -sS https://api.saiku.bi/me/inference/propose \ -X POST -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d "{\"profile\": $PROFILE, \"intent\": \"Sales revenue and order count by customer and date.\"}")
# Happy path: the propose response already includes mondrianXml.echo "$PROPOSAL" | jq -r '.mondrianXml' > sales-cube.xml
# 3. Re-render (only needed if you've edited the proposal).# Important: the render endpoint takes the proposal JSON# directly — NOT wrapped in {proposal: ...}.XML=$(echo "$PROPOSAL" | jq '.proposal' | \ curl -sS https://api.saiku.bi/me/inference/render \ -X POST -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d @- | jq -r '.mondrianXml')
# 4. Save (via /me/schemas — not covered on this page yet)echo "$XML" > sales-cube.xmlRemplacez l’étape 4 par ce dont votre flux a besoin — enregistrer dans votre propre dépôt, commiter dans Git, confier à un relecteur humain.
Et ensuite
- Schema designer — l’interface du dashboard construite sur cette API.
- Authentification — jetons Bearer, limites de débit.
- Serveur MCP — pour les agents LLM qui veulent interroger vos cubes (cette page concerne leur création).