Espaces de travail
Un espace de travail est un jeu d’entrées nommées qui survivent d’un appel à l’autre :
les fichiers que vous déposez, les fichiers que le modèle produit, et les valeurs intermédiaires
qu’il enregistre (une synthèse, des totaux en JSON). Le modèle y accède par des outils, un script
run_code y accède en cellules typées, et votre application y accède par
l’API REST décrite ici.
C’est ce qui permet de trier, agréger ou transformer un tableau sans qu’aucune ligne ne passe
par la réponse du modèle : vous déposez un classeur, le script le lit avec
miraca.workspace.table(...), calcule, et produit un nouveau fichier. Le résultat est exact
quelle que soit la taille du tableau, là où une recopie ligne à ligne dans un appel d’outil est
lente, coûteuse et coupée au plafond de tokens.
Dans l’Assistant, chaque conversation possède son propre espace, alimenté automatiquement par les pièces jointes (voir Fichiers dans le chat). Cette page décrit les espaces que vous nommez et pilotez par l’API.
Trois natures d’entrées
Section intitulée « Trois natures d’entrées »| Nature | D’où elle vient | Ce qu’elle porte |
|---|---|---|
attachment | un fichier déposé (par l’API ou en pièce jointe) | son texte extrait (même moteur que les bases de connaissance) et, pour un classeur, un csv ou un tsv, son rendu tabulaire typé (les montants sont des nombres, les dates des chaînes ISO) |
file | un fichier produit par le modèle ou par un script (create_file, miraca.file.write) | la référence du fichier généré ; son contenu se relit à la demande |
text / json | une valeur écrite par le modèle, par un script, ou par vous | un texte, ou un JSON validé (une entrée json qui ne serait pas du JSON est refusée : elle mentirait sur sa nature) |
Chaque entrée a un nom (unique dans l’espace, insensible à la casse), une origine
(user, model, code, tool) et, quand un rendu tabulaire existe, la liste de ses feuilles avec
leur nombre de lignes et de colonnes. Un nom déjà pris est remplacé.
L’origine tool désigne le résultat entier d’un outil (la réponse JSON d’un connecteur, le
résultat d’un serveur MCP), déposé par Miraca quand il est volumineux : le modèle n’en reçoit
alors que ce qui tient dans sa fenêtre, et relit ou traite le reste depuis l’espace. C’est une
copie de travail. Elle est effacée 24 heures après la dernière activité de la discussion
(pour un espace nommé par l’API : après sa dernière écriture), un espace n’en garde que les
40 plus récentes, et son nom ne porte que le connecteur ou le serveur, l’outil et un condensé,
jamais les arguments de l’appel.
Un nom est rangé en forme Unicode composée (NFC) : un fichier venu de macOS, dont le nom arrive en forme décomposée, est donc rendu avec un nom différent à l’octet près de celui que vous avez envoyé, identique à l’œil. Les deux formes désignent la même entrée, en lecture comme en écriture.
Un nom recopié par un modèle est retrouvé à la typographie près. Un modèle qui recopie un nom lu dans la liste le déforme souvent : une espace insécable devient une espace, une apostrophe typographique est redressée, un accent est recomposé. Quand un nom ne correspond à aucune entrée à l’identique, la lecture et la suppression (par un outil, un script ou l’API) le rapprochent des noms de l’espace : si une seule entrée s’écrit de la même façon, c’est elle. Si plusieurs entrées s’écrivent de la même façon, aucune n’est choisie : l’erreur les nomme. Si aucune ne correspond, l’erreur cite d’abord l’entrée la plus proche, puis la liste de l’espace. L’écriture, elle, garde le nom exact.
Ce que le modèle ou un script écrit ne remplace jamais ce que vous avez déposé. Un fichier
produit ou une valeur écrite sous le nom d’une entrée que vous avez déposée est rangé sous un
nom voisin (ventes (2).xlsx), et la réponse indique le nom retenu. Votre fichier, son tableau et
sa pastille dans la conversation restent intacts. Relire un document de connecteur, ou déposer à
nouveau votre propre fichier, remplace l’entrée comme avant.
Ce que devient un tableau déposé. Les noms de colonnes sont rangés en forme composée, les
blancs (retour à la ligne compris) repliés en une espace et les bords coupés ; une colonne sans
nom s’appelle col_N, et un nom en double reçoit un suffixe (Montant, Montant (2)) : aucune
colonne n’est perdue dans records. Un csv, un tsv ou un txt est lu en UTF-8, ou en windows-1252
quand il est massivement invalide en UTF-8 (l’export « CSV » d’Excel sous Windows) : les accents ne
deviennent plus des caractères de remplacement. Un fichier presque entièrement en UTF-8 valide, avec
quelques octets isolés qui n’en sont pas, reste lu en UTF-8 : ces octets deviennent � et la note
d’extraction en donne le nombre. Les montants à la française (1 234,56, avec
une espace insécable, fine insécable ou fine comme séparateur de milliers) sont des nombres ;
12,500 reste lu comme 12,5, la virgule est toujours une décimale.
Le paramètre workspace sur /v1/chat/completions
Section intitulée « Le paramètre workspace sur /v1/chat/completions »Passez workspace: "<slug>" dans une requête de chat : le modèle reçoit les outils
workspace_list, workspace_read, workspace_write et workspace_delete, avec la consigne de
lire un tableau dans le code plutôt que d’en recopier les lignes. Ce paramètre suffit — vous
n’avez pas besoin d’activer un autre outil pour que l’espace soit lisible. Il reçoit aussi
l’index de l’espace : ce qui s’y trouve, avec le nombre de lignes et la forme des colonnes de
chaque tableau. Un espace inexistant est créé au premier usage. Le paramètre est consommé par
la plateforme : il n’atteint jamais le fournisseur du modèle.
{ "model": "mistral-small", "workspace": "cloture-2026", "tools_enabled": ["code"], "messages": [ { "role": "user", "content": "Trie transactions.xlsx par montant décroissant et produis le classeur." } ]}Un tableau volumineux est RÉSUMÉ dans la question
Section intitulée « Un tableau volumineux est RÉSUMÉ dans la question »Quand vous joignez un classeur ou un csv un peu gros à une question, son contenu n’est pas recopié en entier dans le message envoyé au modèle : celui-ci reçoit un aperçu — les premières lignes de chaque feuille, la forme des colonnes et le nombre réel de lignes — et va chercher le reste dans l’espace de travail quand il en a besoin. Sur un classeur de 172 lignes, cela représente 793 tokens au lieu de 10 650, sans rien perdre : les lignes sont lues par le code, en cellules typées, et les calculs portent sur la totalité.
L’aperçu ne s’applique qu’aux tableaux (xlsx, csv, tsv) au-delà d’une certaine taille, et seulement quand le modèle dispose d’outils pour lire la suite. Un document texte, un PDF ou un petit fichier continuent d’être transmis intégralement.
Le scénario type : déposer d’abord, interroger ensuite. Votre application dépose un fichier
par l’API REST (ci-dessous), puis pose sa question avec le même workspace. Le modèle voit le
fichier dans l’index et, s’il a le scope code, le lit dans un script plutôt que d’en recopier
les lignes. Après la réponse, vous relisez ce qui a été écrit dans l’espace, ou téléchargez le
fichier produit.
Un fichier produit se relit par sa source
Section intitulée « Un fichier produit se relit par sa source »Un fichier produit dans l’espace (document Word, présentation, page HTML, RTF, classeur) se
relit par ce qui l’a produit, pas par ses octets : workspace_read rend le markdown d’un
document, les diapositives d’une présentation (sous la forme des arguments de create_file),
la page telle qu’elle a été écrite, avec une consigne qui dit comment le modifier : rappeler
create_file avec le même nom de fichier et la source entière. La nouvelle version remplace
l’ancienne dans l’espace. La lecture d’un classeur décrit ses feuilles ; sa spec complète,
formules comprises, se lit dans un script :
const spec = JSON.parse(await miraca.workspace.read("budget.xlsx"));spec.sheets[0].rows.push(["Total", { formula: "SUM(B2:B13)" }]);await miraca.file.write(spec); // même nom : une nouvelle versionLes formats dont le fichier est déjà sa source (md, txt, csv, tsv, json, js, svg) se relisent tels quels. Voir Modifier un fichier, ou le reprendre dans une autre discussion.
Un document lu par un connecteur y est déposé automatiquement
Section intitulée « Un document lu par un connecteur y est déposé automatiquement »Quand un script lit un document par un connecteur (un export Google Doc, une page, un
fichier texte : toute réponse qui n’est pas du JSON et dépasse un millier de caractères),
l’hôte le dépose lui-même dans l’espace de travail, sous un nom stable
(<connecteur> · <opération> · <référence> · <condensé>.txt), et le dit à l’assistant. Aux questions
suivantes, l’assistant le relit par workspace_read au lieu de le rechercher et de le
retélécharger : l’historique d’une conversation ne conserve que le texte des messages,
jamais les résultats d’outils, et sans ce dépôt un document lu au premier message était
retrouvé et relu à chaque question. Le condensé porte sur tous les arguments de l’appel :
relire le même document (mêmes arguments, dans n’importe quel ordre) remplace l’entrée, il n’en
crée pas une seconde, et deux documents différents ont toujours deux entrées.
Dans un script run_code
Section intitulée « Dans un script run_code »Avec tools_enabled: ["code"], le script accède à l’espace par miraca.workspace :
// ce qui existe → [{ name, kind, bytes, sheets? }]const entries = await miraca.workspace.list();
// le texte entier d'une entréeconst note = await miraca.workspace.read("synthese.md");
// un tableau en cellules TYPÉES → { sheet, sheets, columns, rows, records, profile }// `rows` ne contient PAS l'en-tête (il est dans `columns`) ; `records` = les mêmes// lignes en objets clé → valeur, plus sûrs qu'un index de colonne ; `profile` = une// fiche par colonne : nature, signe, bornes, somme, valeurs distinctes, vides.const t = await miraca.workspace.table("transactions.xlsx", { sheet: "Transactions" });const montant = t.profile.find((p) => p.name === "Montant");// montant.sign === "négatif" : des dépenses stockées avec leur signe — « le plus gros »// est alors le plus négatif. Le profil est là pour qu'un script (et le modèle) le voie.const total = t.records.reduce((s, r) => s + r.Montant, 0);
// écrire (texte ou objet, sérialisé en JSON) et supprimerawait miraca.workspace.write("totaux.json", { total });await miraca.workspace.delete("brouillon.txt");table() accepte le nom d’une feuille ou son index ; sans sheet, c’est la première. Une
entrée sans rendu tabulaire (un PDF, un texte) lève une erreur explicite : lisez-la avec
read(). À l’inverse, read() sur une entrée qui n’a pas de texte (un fichier Word produit, un
classeur lu comme un texte) lève une erreur, qui renvoie vers table() pour un tableau : elle ne
rend jamais une chaîne vide qu’on lirait comme un document vide.
Les colonnes de records se lisent à la typographie près. r["Montant (€)"] rend la
colonne Montant (€) même quand son nom contient une espace insécable ou un accent décomposé,
quand l’apostrophe est typographique, ou quand le nom a été coupé par … dans un aperçu (si un
seul nom commence ainsi). Un nom qui ne correspond à aucune colonne lève une erreur qui liste
les colonnes, les plus proches d’abord, au lieu de rendre undefined : un total sur une colonne
absente ne se calcule plus en silence. Écrire sous une graphie voisine (r["montant (€)"] = 0)
met à jour cette colonne au lieu d’en créer une seconde. Deux colonnes qui s’écrivent de la même
façon sont signalées comme ambiguës. Les enregistrements restent des objets ordinaires
(JSON.stringify, Object.keys, étalement, miraca.file.write).
Le profil de colonnes. Chaque entrée de profile décrit une colonne : kind
(nombre, texte, date, booléen, vide, mixte), empty (cellules vides), puis
selon la nature min, max, sum, negatives, positives, zeros et sign
(négatif, positif, mixte, nul) pour un nombre, from/to et distinct pour une
date, distinct pour un texte, trues/falses pour un booléen. Le même profil est
donné à l’assistant quand il lit un tableau, et sa forme (nature, signe, vides, sans
aucune valeur) est listée dans son contexte à chaque tour : c’est ce qui lui permet de
voir qu’une consigne comme « du plus gros au plus faible » a deux lectures sur une
colonne entièrement négative, et de dire celle qu’il retient.
L’API REST
Section intitulée « L’API REST »Toutes les routes exigent une clé sk- portant le scope chat. Le propriétaire de l’espace
est celui de la clé (le compte, ou l’organisation pour une clé d’organisation) : une clé ne
voit que ses espaces.
| Route | Rôle |
|---|---|
GET /v1/workspaces | liste les espaces (slug, kind, dates) |
POST /v1/workspaces { slug } | crée un espace (201), ou rend l’existant (200) |
GET /v1/workspaces/{slug} | l’espace et l’index de ses entrées (404 s’il n’existe pas) |
DELETE /v1/workspaces/{slug} | supprime l’espace et ses entrées (204) |
GET /v1/workspaces/{slug}/entries | l’index seul |
POST /v1/workspaces/{slug}/entries | écrit une entrée : JSON { name, kind?, content } ou multipart (champ file, name? pour renommer) ; l’espace est créé s’il n’existe pas |
GET /v1/workspaces/{slug}/entries/{name} | lit une entrée : ?format=text (défaut, par tranches) ou ?format=table&sheet=… ; 404 qui cite l’entrée la plus proche puis la liste de l’espace, 409 si le nom désigne plusieurs entrées |
PUT /v1/workspaces/{slug}/entries/{name} { content, kind? } | remplace le contenu (201 à la création, 200 ensuite) |
DELETE /v1/workspaces/{slug}/entries/{name} | supprime l’entrée (204, 404 si absente, 409 si le nom désigne plusieurs entrées) |
Un slug fait de 1 à 64 caractères (lettres, chiffres, ., -, _, :), commence par une
lettre ou un chiffre, et se compare sans la casse. Le nom d’une entrée s’encode dans l’URL
(encodeURIComponent) : les espaces et les accents y passent. En lecture et en suppression, un
nom qui ne correspond à aucune entrée à l’identique est rapproché des noms de l’espace à la
typographie près (espace insécable, apostrophe typographique, forme Unicode) ; la réponse rend le
nom réel de l’entrée.
Déposer un fichier
Section intitulée « Déposer un fichier »curl -X POST https://api.miraca.fr/v1/workspaces/cloture-2026/entries \ -H "Authorization: Bearer sk-..." \ -F "file=@transactions.xlsx"{ "entry": { "name": "transactions.xlsx", "kind": "attachment", "origin": "user", "mime": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "bytes": 18432, "sheets": [{ "name": "Transactions", "rows": 172, "columns": 11 }], "file_id": null, "updated_at": "2026-09-09T14:03:11.000Z" }, "extraction": { "truncated": false }}Formats acceptés : ceux des fichiers dans le chat (PDF, Word, Excel, CSV,
TSV, texte, Markdown, HTML, RTF), 15 Mo au plus (413 au-delà). Un fichier illisible (PDF
scanné, format inconnu) est refusé en 400 avec le motif. Le champ name renomme l’entrée ;
comme c’est l’extension qui décide du format lu, un nom donné sans extension hérite de
celle du fichier. truncated vaut true quand le rendu tabulaire dépasse 50 000 lignes par
feuille : les premières lignes sont conservées, et le texte l’annonce.
Lire un tableau en cellules typées
Section intitulée « Lire un tableau en cellules typées »curl "https://api.miraca.fr/v1/workspaces/cloture-2026/entries/transactions.xlsx?format=table&sheet=Transactions" \ -H "Authorization: Bearer sk-..."{ "name": "transactions.xlsx", "sheet": "Transactions", "sheets": ["Transactions", "Paramètres"], "columns": ["Date", "Libellé", "Montant", "Catégorie"], "rows": [ ["2026-01-05", "Frais bancaires", -76.37, "Frais bancaires"], ["2026-01-06", "Virement client", 1200, "Ventes"] ], "profile": [ { "name": "Date", "kind": "date", "from": "2026-01-05", "to": "2026-01-06", "distinct": 2, "empty": 0, "filled": 2 }, { "name": "Libellé", "kind": "texte", "distinct": 2, "empty": 0, "filled": 2 }, { "name": "Montant", "kind": "nombre", "sign": "mixte", "min": -76.37, "max": 1200, "sum": 1123.63, "negatives": 1, "positives": 1, "zeros": 0, "empty": 0, "filled": 2 }, { "name": "Catégorie", "kind": "texte", "distinct": 2, "empty": 0, "filled": 2 } ]}rows ne porte pas l’en-tête. profile est la fiche de chaque colonne (voir « Dans un
script run_code ») ; la liste des entrées porte la même information sous forme réduite,
sheets[].fields (nature, signe, vides), sans aucune valeur. Sans rendu tabulaire (un PDF), ou pour une feuille inconnue, la
réponse est un 400 qui nomme les feuilles disponibles.
Lire un texte par tranches
Section intitulée « Lire un texte par tranches »curl "https://api.miraca.fr/v1/workspaces/cloture-2026/entries/synthese.md?offset=0&limit=8000" \ -H "Authorization: Bearer sk-..."La réponse porte text, offset, next_offset (null quand il ne reste rien) et
total_chars, avec les métadonnées de l’entrée. limit vaut 8 000 caractères par défaut,
32 000 au plus. Pour une pièce jointe, text est le texte extrait (une table Markdown par
feuille pour un classeur) ; pour un fichier binaire produit (Word, PowerPoint), il n’y a pas de
texte et la réponse le dit (note). ⚠️ Une entrée file dont le fichier sous-jacent a été
supprimé (rétention, ou suppression manuelle depuis la page Fichiers) le dit aussi, mais
avec une note DISTINCTE (deleted: true) : ce n’est pas un format non lisible, c’est un
fichier qu’il faut régénérer.
Écrire une valeur
Section intitulée « Écrire une valeur »curl -X POST https://api.miraca.fr/v1/workspaces/cloture-2026/entries \ -H "Authorization: Bearer sk-..." -H "Content-Type: application/json" \ -d '{ "name": "parametres.json", "kind": "json", "content": { "exercice": 2026, "seuil": 500 } }'content accepte une chaîne ou un objet (sérialisé côté serveur, forme pratique depuis un
outil no-code). Plafond : 4 Mo par entrée, 200 entrées par espace.
Sécurité
Section intitulée « Sécurité »- Le propriétaire est la clé. Un espace n’est jamais désigné par un identifiant de compte dans la requête : c’est la clé qui dit à qui il appartient, et une clé d’organisation ne voit que les espaces de l’organisation.
- Le contenu est chiffré au repos, sous une clé propre à chaque espace (même schéma à enveloppe que les bases de connaissance et les conversations). Seules les métadonnées (noms, natures, tailles, feuilles) restent en clair : c’est ce que l’index liste.
- Les espaces de conversation sont inaccessibles par clé. Leur slug commence par
conversation:; ce préfixe est refusé par l’API (400) comme par le paramètreworkspace. Une conversation appartient à une personne, pas à une clé. - Supprimer un espace supprime ses entrées. Les fichiers produits
référencés par une entrée
filerestent, eux, soumis à leur propre conservation.