API et MCP

Un seul endpoint, 1008 déclarations, et 331 d'entre elles sont à vous.

Il n'existe aucune table de routage sur cette plateforme. http_module enregistre exactement deux handlers Express, app.post("*") et app.get("*"), lit le troisième segment du chemin /cpi/<method>, et recherche ce nom dans l'objet cpi_methods de config.json. Cet objet contient 1008 entrées. Un nom absent renvoie {code: -1, data: 'method "X" not exist'} avant même la lecture d'une quelconque credential, dans cpi/http_module/index.mjs lignes 634 à 650. Ajouter une méthode à cet objet, c'est l'intégralité de l'acte de publier une API : c'est ce qui rend la méthode appelable en HTTP, visible comme outil MCP, soumise au plan, mise en cache, journalisée pour l'audit et suivie comme job. Il n'existe pas de second endroit où l'enregistrer, ni de client généré à régénérer. 677 des 1008 entrées portent private: true, le mur côté opérateur qu'aucune clé API ne franchit, quel que soit le mode, si bien que ce qu'un appelant externe peut réellement atteindre se limite à 331 méthodes, et le catalogue REST comme la liste d'outils MCP sont compilés à partir de ce même ensemble. La suite de cette page suit un appel à travers les sept étapes qu'il traverse, montre la transcription réelle dans les deux sens, et nomme les endroits où le code livré fait moins que ce que l'interface autour de lui laisse entendre.

POST /cpi/<method>, GET fonctionne aussi
Cinq chaînes de clearance, une seule porte
331 méthodes, 331 outils MCP
Un seul endpoint, 1008 déclarations, et 331 d'entre elles sont à vous.
1008

Méthodes déclarées dans un seul fichier de config

331

Accessibles avec une clé API développeur

5

Chaînes de clearance pour toute la surface

2003

Définitions de champs typés, dont 1102 obligatoires

Le chemin de la requête

Chaque appel traverse les sept mêmes étapes, avec quatre sorties possibles avant que votre handler ne s'exécute.

Une méthode, c'est un nom, et chaque nom emprunte le chemin identique. Le trafic public arrive sur router_module, qui termine le 443, aiguille sur le premier segment du chemin et achemine le cas cpi directement vers localhost:3007. À partir de là, l'ordre est fixe et mérite d'être retenu, car une étape se situe là où la plupart des lecteurs ne l'attendent pas : le contrat de champs typés est parcouru AVANT l'authentification, pas après, si bien qu'une requête à laquelle il manque un argument obligatoire est refusée sans que la plateforme n'ait jamais regardé votre credential. L'authentification elle-même est un simple appel de broker vers session_module plutôt qu'un middleware, les deux verrous de compte lisent un seul et même document de compte entre eux, et le handler est atteint via RabbitMQ plutôt que par un import. Rien ici ne bifurque selon le nom de la méthode, sauf la recherche à l'étape deux.

Deux éléments que ce schéma ne contient délibérément pas. Il n'y a aucun limiteur de débit sur le chemin cpi : router_module définit trois compartiments express-rate-limit, montés sur /login et sur quatre routes de formulaire, alors que le cas cpi est un simple proxy sans aucun middleware devant lui. Les appels authentifiés sont bornés par le périmètre de la credential, pas par un débit de requêtes. Et il n'existe nulle part dans ce chemin de blocage après échecs répétés ; rien dans les fichiers lus ne compte ni ne suspend une clé qui continue d'être refusée.
Sur le fil

Le fil, dans ses deux moitiés.

Trois choses surprennent ici, elles méritent donc d'être vues plutôt que racontées. Premièrement, la clé développeur voyage dans le corps JSON sous le nom api_key. http_module ne lit jamais d'en-tête Authorization sur ce chemin, et session_module déstructure api_key directement depuis la requête à la ligne 116, si bien qu'un client qui place la clé dans un en-tête s'authentifie comme personne. Deuxièmement, le succès et l'échec sont le même objet : {code, data}, avec un code de 1 ou plus pour un succès et un nombre négatif pour tout le reste. Troisièmement, le statut HTTP est dérivé du code plutôt que choisi, par get_status aux lignes 204 à 210 : seul un code compris entre -400 et -499 devient son propre statut, si bien que -401 est un vrai 401 et -428 un vrai 428, tandis que tout le reste, y compris le très fréquent -1, se réduit à un simple 400. Un client qui bifurque uniquement sur le statut ne peut pas distinguer un mauvais argument d'un handler qui a planté. Le seul en-tête de réponse exposé via CORS est xu-region, tamponné avec XUDA_HOSTNAME.

POST https://xuda.ai/cpi/get_plugins
content-type: application/json (GET fonctionne aussi, la query string est analysée dans le corps)
{ "app_id": "app_...", "api_key": "pk_..." }
la clé est un champ du corps. Il n'y a aucun en-tête Authorization sur ce chemin
< 200 xu-region: eu1
{ "code": 1, "data": [ ... ] }
un échec est le même objet, et le statut est dérivé du signe
{ "code": -401, "data": "gtp_token expired" } 401
{ "code": -1, "data": "Error: 'App Name' is a mandatory field" } 400
{ "code": -428, "verification_required": true } 428
seules les plages -400 à -499 deviennent leur propre statut. -1 est le code le plus fréquent, et c'est toujours 400.
Lisez le code, pas le statut. La ligne 400 et la ligne 401 sont toutes deux des échecs ordinaires du même endpoint, et seule la seconde porte un statut qui dit quelque chose de précis.
Le mur

private est le mur, et il constitue la majorité du catalogue.

clearance décide qui l'appelant doit être. private décide si une credential autre qu'une session navigateur peut même tenter la méthode, et c'est ce drapeau qui définit la forme du produit vue de l'extérieur. 677 des 1008 méthodes déclarées portent private: true. Aucun mode de clé ne franchit cette ligne : ni une clé full-access, ni une clé personnalisée avec la méthode inscrite dans sa liste blanche, ni une session MCP. Ce qui reste, ce sont 331 méthodes, et ce même 331 est ce que voit tout consommateur externe, qu'il arrive via curl ou via un agent muni d'une liste d'outils. Les quatre points ci-dessous détaillent la mécanique de ce mur, y compris la partie qui relève de la convention plutôt que de la garantie.

  • La branche clé API n'est empruntée que lorsque la méthode n'est ni privée ni possédée par api_module : session_module lignes 121 à 130 lit if (api_key and method.microservice is not api_module and not method.private). Une méthode privée n'atteint donc jamais la validation de clé. Elle retombe sur le chemin de session à la place, que seul un appelant par clé ne peut satisfaire, si bien que le refus se lit comme un échec d'authentification plutôt que comme un 404. api_module lignes 172 à 181 impose la même règle une seconde fois avec le message littéral api method <name> is unauthorized.
  • Les 8 méthodes de gestion de clés sont elles-mêmes privées : create_account_api_key, update_account_api_key, get_account_api_keys, create_app_api_key, update_app_api_key, get_app_api_keys, get_account_system_api_methods et get_app_system_api_methods. Une clé ne peut donc jamais frapper une clé, en faire tourner une, ni énumérer les clés du compte auquel elle appartient. Le cycle de vie des clés est une action réservée à une session de dashboard, et c'est une propriété du catalogue plutôt qu'une règle écrite quelque part.
  • mcp_module ignore private lorsqu'il enregistre les outils, si bien que REST et MCP sont compilés à partir de l'ensemble identique. Une clé full-access énumère Object.keys(cpi_methods), écarte celles marquées privées, et enregistre les 331 restantes comme outils avec un schéma d'entrée zod dérivé des champs déclarés de chaque méthode. La liste est résolue à neuf à chaque session plutôt que figée dans la clé, si bien qu'une méthode livrée après l'émission de la clé est prise en compte sans qu'il faille modifier celle-ci.
  • Le point honnête : private est absent plutôt qu'à false sur 321 des entrées, et seules 10 déclarent private: false explicitement. Le mur est défini par la fausseté (falsiness), ce qui signifie qu'une méthode nouvellement livrée sans champ private est appelable de l'extérieur par défaut. C'est un défaut, pas une décision, et c'est la seule chose sur cette page qui mérite d'être vérifiée par rapport à vos propres attentes avant de vous fier aux 331.
Le catalogue, découpé
Déclarées dans cpi_methods
1008
private: true, réservé à l'opérateur
677
Accessibles avec une clé
331
Sélectionnées pour l'agent maison de Xuda (mcp: true)
22
un indice de classement interne à ai_module, pas la porte MCP
Clearance

Cinq chaînes de clearance, et ce que chacune vous demande réellement.

Une seule chaîne sur l'entrée de config décide de la credential que l'appelant doit présenter, et le dispatcher ne bifurque jamais selon le nom de la méthode pour le déterminer : http_module transmet method.clearance à session_module.validate_cpi_access et laisse cette unique fonction répondre. Les décomptes et les codes de succès ci-dessous sont lus directement dans session_module lignes 115 à 291, pas paraphrasés d'une documentation. La ligne qui mérite qu'on s'y attarde est admin. clearance admin est un rôle d'application, pas un rôle plateforme : il se résout à partir d'une ligne de la vue user_active_shares de xuda_team, si bien qu'un admin d'équipe sur un projet satisfait n'importe laquelle des 252 méthodes admin sur ce projet et rien au-delà. Le superuser plateforme est un concept distinct qui n'est même pas une valeur de clearance. C'est un tableau de config à exactement une entrée, vérifié comme un contournement qui renvoie le code 104 et force auth_clearance à admin, ce qui signifie qu'il ne peut jamais exprimer plus qu'admin. Toute méthode qui doit être réservée au personnel vérifie donc elle-même dans son propre corps, et 83 emplacements répartis sur 25 fichiers de modules font exactement cela en renvoyant {code:-403, data:'superuser only'}. C'est une convention plutôt qu'un mécanisme : rien dans le dispatcher ne vérifie qu'une méthode réservée au personnel porte effectivement son propre contrôle.

Méthodes
Ce que l'appelant doit présenter
Ce que le verrou renvoie
public51rien du tout, le contrôle renvoie avant qu'aucune credential ne soit luecode 100
user680un document de session actif dans xuda_sessions, sans app_idcode 106
user_access5app_id plus une ligne d'équipe user_group, developer ou admincode 6
developer15app_id plus developer ou admincode 6
admin252app_id plus admin, ou être le propriétaire de l'appcode 6, correspondance propriétaire 106
superuserpas une clearanceun uid dans le tableau superuser_account_ids à une seule entréecode 104, forcé à admin
vide ou absent5rien ne le satisfait : une clearance fausse (falsy) lève une exception avant toute autre branchecode -401. NON VÉRIFIÉ si ces cinq entrées sont réservées au broker uniquement
Deux portes d'entrée

Deux portes d'entrée, un seul verrou.

mcp_module fait tourner sa propre application Express sur le port 3012 et parle Streamable HTTP sur /mcp, ce qui lui donne l'air d'être un second modèle de permission. Ce n'en est pas un. Chaque handler d'outil qu'il enregistre effectue un simple fetch vers http://localhost:3007/cpi/<method> avec l'api_key déplacée hors de l'en-tête Authorization et vers le corps JSON, si bien que la clearance n'est vérifiée qu'une seule fois, dans le même appel validate_cpi_access, que l'appelant soit curl ou un agent. Les cinq lignes ci-dessous sont appariées à dessein : lisez-les côte à côte et vous verrez précisément quelle moitié de l'histoire change entre les deux portes, et à quel point c'est peu.

L'id de session est renvoyé sous mcp-session-id et fermé de force après un TTL fixe de 10 minutes. Une chose que la porte MCP ajoute et que la porte REST ne peut pas : à l'initialisation, le serveur injecte le guide d'auteur du studio maison de Xuda comme instructions MCP, si bien qu'un modèle qui se connecte lit le flux de travail avant son premier appel d'outil.
Par module

Le classement s'inverse : les modules les plus volumineux exposent le moins.

Trente-cinq modules possèdent les 1008 méthodes déclarées, comptées à partir du champ microservice de chaque entrée de config. Triée par volume de méthodes déclarées, la liste se lit comme une carte de la plateforme : app_module 110, ai_module 91, account_module 90, drive_module 77, stripe_module 75. Triée par ce qu'une clé externe peut réellement atteindre, cet ordre se retourne complètement, car la seconde colonne n'est jamais que la première moins tout ce qui porte private:true. Sept modules n'exposent rien du tout, et ce ne sont pas les plus petits : stripe_module déclare 75 méthodes et n'en publie aucune, marketplace_module 25 et aucune, shipping_module 26 et aucune, commerce_module 21 et aucune, et bot_protection_module ainsi que jobs_module les rejoignent. La surface financière et la surface boutique sont entièrement internes, accessibles uniquement depuis une session dashboard connectée avec la bonne clearance, ce qui est une forme délibérée plutôt qu'un trou de documentation. La dernière ligne est la plus tranchante de toutes : les huit méthodes de gestion de clés d'api_module sont toutes privées, si bien qu'une clé ne peut jamais en frapper une nouvelle, en faire tourner une, ni même énumérer les clés existantes. Lisez d'abord les zéros, puis lisez le haut du tableau. Une ligne mérite un second regard plutôt qu'un gros titre : voice_module affiche 46 méthodes sur 46 accessibles de l'extérieur, le seul module de toute la plateforme sans aucune méthode privée, et cela mérite d'être vérifié par rapport à l'intention.

Déclarées
Accessibles avec une clé
Drive7766
App11061
Voice4646
Account9032
IA9126
Team5014
Stripe750
MarketPlace250
Shipping260
Commerce210
Clés API80, une clé ne peut jamais gérer de clés
Démarrer

Trois gestes pour passer de rien à un appel qui fonctionne.

Le dashboard ne vous remet pas une page de démarrage, il vous remet quatre onglets à coller-exécuter dans l'écran Clés API : MCP, curl, node et php, avec MCP comme onglet par défaut et votre propre première clé déjà substituée dans l'extrait. Autant le dire clairement avant de commencer : il n'existe pas de SDK REST. L'onglet node est un simple appel fetch et l'onglet php un simple bloc curl_init, si bien que ce contre quoi vous intégrez est un endpoint HTTP et une enveloppe JSON, pas une bibliothèque cliente typée.

  1. 01

    Frapper une clé

    Deux portées, compte ou app, et deux modes d'accès. Une clé full-access n'est vérifiée que contre private, et ce contrôle s'exécute à neuf à chaque appel plutôt que d'être figé au moment de l'émission, si bien que les méthodes livrées après la création de la clé sont couvertes sans qu'il faille y toucher. Une clé personnalisée porte à la place une liste api_allow_methods, avec une exception à connaître : les méthodes en clearance public contournent entièrement la liste blanche, si bien que les 51 méthodes anonymes sont accessibles avec n'importe quelle clé valide.

  2. 02

    Demandez au catalogue ce que vous pouvez appeler

    get_account_system_api_methods renvoie les 331 méthodes non privées sous forme de {description, fields, category}, où category est le microservice propriétaire. C'est la même donnée à partir de laquelle les schémas d'outils MCP sont compilés, si bien que le catalogue REST et la liste d'outils de l'agent ne peuvent jamais diverger. get_app_system_api_methods est la vue restreinte à l'app et renvoie 86, l'ensemble admin non privé. Les deux sont elles-mêmes privées, donc vous les lisez depuis une session dashboard.

  3. 03

    Appelez-la, ou confiez la même clé à un agent

    POST /cpi/<method> avec api_key dans le corps, ou lancez claude mcp add contre /mcp avec la clé comme bearer token. Même verrou, mêmes 331 méthodes, deux enveloppes. Une clé émise pour votre backend et une clé émise pour un agent ne diffèrent en rien, sauf la porte vers laquelle vous les orientez, c'est pourquoi restreindre ce qu'un agent peut faire consiste à émettre une clé plus étroite plutôt qu'à configurer l'agent.

Appels longs

Un appel long répond immédiatement et se termine via un socket.

Ce que la plupart des intégrateurs comprennent à l'envers : le chemin job est le comportement par défaut. http_module l'active sauf si l'entrée de la méthode indique job:false, et 573 des 1008 entrées font ce choix, ce qui en laisse 435 qui restent dedans. Ces 435, ce sont les déploiements, les builds, les réinstallations, le travail qui n'allait jamais tenir dans une seule requête. Un job est enregistré avant l'envoi dans les deux cas, si bien que la seule question est de savoir si vous avez demandé qu'on vous remette le ticket plutôt que le résultat. Une limite dans le même souffle : certaines méthodes finalisent elles-mêmes leur propre job, si bien que le compteur d'étapes est indicatif plutôt qu'une garantie.

  • Envoyez job:true dans le corps et l'appel renvoie un ticket portant l'id du job, tandis que le résultat réel arrive plus tard via socket.io sur le port 4010. Deux relevés de vérification ne s'accordent pas sur le code numérique de ce ticket, l'un lisant 88 et l'autre 999 avec l'info "job successfully scheduled to run immediately", donc considérez ce nombre comme NON VÉRIFIÉ et fiez-vous à l'id du job.
  • job_steps est littéralement le texte de la barre de progression, pas une machine à états interne. 38 méthodes en déclarent une : build_app en déclare 7, reinstall_spa_resources en déclare 18, du lancement du processus jusqu'à l'activation du serveur. Un job sans étapes déclarées se voit attribuer un total de 2.
  • job_title est un template lodash rendu contre le corps de votre propre requête, ce qui explique pourquoi le toast affiche Reinstalling SPA Server plutôt qu'un nom de méthode. 36 méthodes en déclarent un.
  • broadcast:true sur 194 méthodes est ce qui rend la mise à jour non silencieuse ; tout le reste est suivi discrètement. job_timeout plafonne l'attente proxifiée et vaut par défaut 1800 secondes, et 7 méthodes le redéfinissent.
Un appel long
  1. register_job
    un document de job dans memcached, TTL de 600 secondes
  2. Vous recevez un ticket, pas un résultat
    uniquement lorsque l'appelant l'a demandé en envoyant job:true
  3. Le worker rapporte la progression
    update_job parcourt les job_steps déclarées
  4. socket.io, port 4010
    dans la salle rejointe par votre navigateur ; stat 3 en cas de succès, 4 en cas d'échec
Une entrée, sept lecteurs

Une déclaration, sept consommateurs.

Les clés d'une entrée cpi_methods ne sont pas de la documentation. Chacune est lue par un sous-système différent, à un moment différent, et aucun de ces sous-systèmes ne connaît les autres. clearance est lue par le verrou de session. private est lue par le validateur de clé API et, séparément, par l'enregistreur d'outils MCP. fields est parcourue par le vérificateur qui s'exécute avant l'authentification et compilée en schéma zod lorsqu'un agent ouvre une session. job est lue par la file d'attente, cache par memcached et par l'en-tête d'edge, min_plan par le verrou de plan, security par l'écrivain de journal. Ajouter une méthode à config.json décide des sept d'un seul coup, ce qui explique pourquoi il n'existe ni table de routage, ni fichier de permissions, ni schéma séparé à maintenir en phase.

Deux détails de cette ligne de cache méritent d'être connus avant de s'y fier. La clé est construite en retirant gtp_token, job_id, token_ret et app_token du corps de la requête, puis en sérialisant ce qui reste, si bien que uid demeure présent et que les entrées restent propres à chaque utilisateur plutôt que partagées à l'échelle du compte. Et une méthode marquée cache:true sans cache_timeout est écrite avec expiration 0, ce que memcached interprète comme une absence totale d'expiration, si bien que cette entrée vit jusqu'au redémarrage de la machine ou jusqu'à ce que quelqu'un la supprime à la main.
Connecter un agent

Connecter un agent tient en une ligne.

Voici toute la surface d'intégration. L'endpoint MCP est un serveur Streamable HTTP sur le port 3012, exposé sur /mcp, et c'est le seul endroit de la plateforme où une clé API voyage comme en-tête : tout autre chemin, y compris l'appel en boucle locale que fait chaque outil vers /cpi, lit api_key dans le corps JSON. Deux faits déterminent si cela fonctionne à votre échelle. La liste d'outils est résolue par session plutôt que figée dans la clé, si bien qu'une clé full-access prend en compte les méthodes livrées après son émission sans qu'il faille la modifier ni redéployer. Et cette même clé remet à un modèle les 331 outils en une seule liste, sans regroupement ni pagination, ce qui dépasse ce que la plupart des modèles sélectionnent correctement. La seule façon de la restreindre est d'émettre une clé plus étroite, car une clé personnalisée n'enregistre exactement que ses api_allow_methods et rien d'autre.

claude mcp add --transport http xuda https://xuda.ai/mcp \
--header "Authorization: Bearer pk_..."
n'importe quel autre client : la même URL, le même en-tête
331 outils enregistrés, résolus à neuf à l'ouverture de session
une clé full-access prend en compte les méthodes livrées après son émission
id de session renvoyé sous mcp-session-id, fermé après 10 minutes
une méthode avec une spécification de champ malformée est ignorée avec un avertissement console,
si bien qu'une session peut enregistrer moins de 331 outils
xu-channel: mcp
défini sur l'appel en boucle locale, ce qui distingue le trafic agent
du trafic REST dans votre journal de compte
Les lignes grises sont les nôtres. Les deux lignes en accent en haut sont ce que le dashboard imprime textuellement avec votre propre clé substituée, et la ligne xu-channel est un en-tête de requête que mcp_module définit sur l'appel en boucle locale, pas quelque chose que vous envoyez.
Limites honnêtes

Neuf choses que cette API ne fait pas, dont trois que le dashboard laisse pourtant entendre.

  • Une clé a quatre états dans l'interface, 1 draft, 2 suspended, 3 active et 4 delete, et seul l'état 4 est rejeté. Une clé en brouillon ou suspendue s'authentifie et s'exécute quand même, sur REST comme sur MCP. Le contrôle se trouve dans cpi/api_module/index.mjs lignes 131 à 199.
  • La liste blanche de domaines est stockée sur la clé et modifiable dans l'interface, mais toute branche censée l'appliquer est commentée. Ne concevez pas votre architecture autour d'elle.
  • Il n'y a aucune limite de débit sur /cpi. Les trois limiteurs de router_module sont montés sur /login et quatre routes de formulaire, et le cas cpi passe directement au port 3007 sans aucun middleware devant lui. Les méthodes publiques sont protégées par le produit de protection anti-bot, pas par le dispatcher.
  • Les codes négatifs se réduisent. Seule la plage de -400 à -499 se projette sur son propre statut HTTP, si bien que -401 et -403 arrivent comme 401 et 403 et que tout le reste, y compris le très fréquent -1, devient un simple 400. Lisez le code et les data, jamais le statut seul.
  • private relève de la fausseté (falsiness) plutôt que d'une déclaration. 321 entrées omettent totalement la clé, si bien qu'une méthode nouvellement livrée est appelable par clé, sauf si quelqu'un pense à la marquer.
  • Cinq entrées ne portent aucune clearance utilisable, trois une chaîne vide et deux rien du tout, et le verrou lève une exception sur une clearance fausse. Elles devraient échouer franchement en HTTP. On ne sait pas si ce sont des méthodes internes réservées au broker qui ne voient jamais de requête.
  • Le catalogue restreint à l'app filtre sur les valeurs de clearance studio et user_group, que pourtant aucune méthode ne porte, si bien qu'il renvoie les 86 méthodes admin et omet silencieusement les 5 méthodes user_access, qui incluent dbs_read et get_app_info.
  • MCP Server apparaît comme un type de déploiement dans l'assistant et dans la documentation, et deploy_module le refuse avec le code -2 et le message deploy_type in beta. La surface MCP livrée est l'unique endpoint de la plateforme rattaché à une clé API, pas un déploiement par projet.
  • Chaque décompte de cette page a été lu dans la copie du dépôt de config.json. config_dev.json affiche 1026 méthodes déclarées et 334 non privées, et la config master en production n'a pas été lue, donc considérez ces chiffres comme une indication de forme plutôt que comme le total exact du jour. Le fait que voice_module affiche 46 méthodes sur 46 non privées mérite d'être confirmé par rapport à l'intention.
Avant de construire

Les questions que vous êtes en train de vous poser

Six choses que la référence ne vous dira pas, répondues face au code livré plutôt qu'à l'intention.

Observer

Regardez un appel partir et revenir.

Lisez le catalogue avant d'écrire la moindre ligne contre lui.

Un seul appel, get_account_system_api_methods, vous remet chaque méthode à laquelle vous pouvez accéder, chacune réduite à sa description, ses champs typés et le microservice qui la possède. C'est exactement la déclaration à partir de laquelle les schémas d'outils MCP sont compilés, si bien que la liste remise à un agent et celle contre laquelle vous codez sortent d'un seul fichier plutôt que de deux documents qui divergent. Lisez-la d'abord, décidez quelles méthodes vous avez vraiment nommées, puis émettez une clé personnalisée ne contenant que celles-ci. Une clé full-access est la façon la plus rapide de démarrer et la plus large chose que vous puissiez confier à un modèle.