Version 1.0.0
Le relais de la passerelle
L'interface HTTP sur laquelle l'éditeur s'engage : adressage, transformations, flux, refus et bornes de transport.
Cette page décrit ce qu’un programme tiers peut écrire contre la passerelle. C’est la partie contractuelle de son interface : ce qui y figure ne change pas sans que le changement soit annoncé.
Ce qui relève de l’exploitation d’un déploiement (variables d’environnement, rôles, tables) vit sur trois autres pages, marquées « interne ».
Trois surfaces la composent : le relais, par lequel on appelle un modèle, la documentation hors ligne, que la passerelle sert quand l’exploitant l’a installée, et le service de questions par lequel un agent l’interroge.
Le relais
Section intitulée « Le relais »ALL /:endpoint/*
La route par laquelle un programme tiers appelle un modèle. Le premier segment du chemin nomme un endpoint déclaré par l’exploitant ; tout ce qui suit (chemin et chaîne de requête) est concaténé derrière l’adresse de base du fournisseur. Toutes les méthodes HTTP y passent. C’est un attrape-tout : il prend tout premier segment que les routes montées avant lui n’ont pas réservé.
La passerelle concatène : l’adresse de base du fournisseur, telle que l’exploitant l’a déclarée, suivie de tout ce qui reste du chemin après le premier segment, chaîne de requête comprise.
appel https://passerelle.interne/sonnet/v1/messages?beta=1endpoint sonnetrelayé <adresse de base du fournisseur>/v1/messages?beta=1Trois conséquences que l’adressage impose :
- le premier segment est retiré par sa longueur, donc lui seul. Un chemin qui répète le nom de l’endpoint plus loin le conserve ;
- le chemin part tel que l’appelant l’a écrit, sans être remis en forme ;
/et//ne sont pas des adresses de la passerelle. Elles ne portent aucun premier segment, la route ne les reconnaît pas, et la réponse est celle du cadre HTTP sous-jacent : du texte, pas l’enveloppe JSON décrite plus bas.
Où l’appelant pose sa preuve d’identité
Section intitulée « Où l’appelant pose sa preuve d’identité »Deux en-têtes, et aucun autre :
Authorization: Bearer <jeton>x-api-key: <clé>Les deux sont retirés de la requête avant qu’elle parte chez le fournisseur : la preuve présentée à la passerelle ne voyage pas au-delà d’elle.
Ce que la passerelle change dans la requête
Section intitulée « Ce que la passerelle change dans la requête »Ces en-têtes sont retirés :
hostaccept-encodingauthorizationx-api-keycontent-length
À la place de la preuve retirée, la passerelle pose la clé du fournisseur, dans l’en-tête que l’exploitant a déclaré pour lui.
Le corps est relayé tel quel, à une exception près : s’il s’analyse comme du JSON, son champ
model est écrasé par le modèle de l’endpoint visé. Un corps qui ne s’analyse pas comme
du JSON part inchangé.
La passerelle ne valide ni le schéma du corps, ni son type de contenu, ni sa taille.
Ce qu’elle change dans la réponse
Section intitulée « Ce qu’elle change dans la réponse »Ces en-têtes sont retirés :
content-encodingtransfer-encoding
Le statut, les autres en-têtes et le corps du fournisseur sont relayés tels quels.
La réponse est relayée au fil de l’eau dès que l’une des deux conditions est remplie :
- l’appelant a demandé
"stream": truedans un corps JSON ; - le fournisseur répond avec un type de contenu
text/event-stream.
L’une suffit : un fournisseur qui omet le type de contenu ne doit pas ramener l’échange à une réponse mise en tampon.
En flux, ces en-têtes disparaissent en plus :
content-length
Les refus que le relais prononce lui-même
Section intitulée « Les refus que le relais prononce lui-même »Ils portent tous la même enveloppe, { "error": "<phrase>" }, sauf le 404 d’endpoint
inconnu, qui porte un second champ.
Un refus prononcé par le fournisseur n’est pas dans cette table : il est relayé tel quel, avec son propre corps. Ceux de la documentation hors ligne ont leur propre table, plus bas.
| Code | Quand | Corps |
|---|---|---|
| 401 | Aucune preuve d’identité utilisable n’accompagne la requête : ni Authorization: Bearer, ni x-api-key, ou bien l’un des deux est vide. | { "error": "Missing credentials. Set apiKey in your Lemniscate config." } |
| 401 | Une preuve est présentée et refusée. La passerelle ne dit pas pourquoi : le motif part au journal des décisions, parce que dire à l’appelant ce qui n’a pas convenu l’aide à deviner ce qui aurait convenu. | { "error": "Unauthorized." } |
| 403 | Le sujet figure sur la liste de révocation de l’instance, et sa révocation n’a pas été levée. Le motif et l’auteur de la révocation restent au journal. | { "error": "Your access to this instance has been revoked. Contact the operator of this deployment." } |
| 403 | L’appel nomme, par l’en-tête x-lemniscate-session, une session d’agent que l’administration a révoquée, ou un sous-agent d’une telle session. Seule cette session est refusée : la même personne, sous une autre session ou sans en nommer, est servie. La réponse porte l’instant de la révocation et l’en-tête x-lemniscate-session-standing: revoked ; l’auteur et le motif restent au journal. | { "error": "This agent session was revoked by your organization's administration.", "code": "agent-session-revoked", "revokedAt": "<instant ISO>" } |
| 404 | Le premier segment du chemin ne nomme aucun endpoint déclaré. C’est le seul refus dont le corps porte un second champ : available liste les noms d’endpoints existants. | { "error": "Unknown endpoint: <nom>", "available": ["<nom>", "…"] } |
| 403 | L’endpoint existe mais l’exploitant l’a désactivé. | { "error": "Endpoint \"<nom>\" is disabled." } |
| 403 | La licence du déploiement ne couvre pas cette requête. Trois motifs la conduisent ici, et le corps rendu dit lequel : le nombre de sièges licenciés est atteint pour le mois en cours, la capacité demandée n’est pas incluse dans la licence, ou la licence a expiré et sa période de grâce est écoulée. Le contrôle est placé après la résolution de l’endpoint (il faut son nom pour juger de la capacité) et avant l’autorisation : un déploiement sans droit de licence n’a pas à consulter la politique de rôles. Ce refus n’existe que sur le profil on-premise : en serverless, les limites commerciales passent par la facturation. | { "error": "The licensed seat limit for this deployment has been reached for the current month. Contact the operator to increase the seat count." } — ou { "error": "The capability \"<nom>\" is not included in this deployment's license." } — ou { "error": "The deployment license has expired and the grace period has elapsed. Contact the operator to renew the license." } |
| 403 | L’identité est établie, et aucun rôle porté par le sujet ne donne le droit d’appeler cet endpoint. C’est aussi l’état de tout le monde sur une instance neuve : sans rôle attribué, l’ensemble des permissions est vide. | { "error": "Your identity is recognised, but no role you hold carries the right to call this endpoint." } |
| 403 | Aucun plan n’est affecté au compte. Distinct du plafond atteint : aucun paiement ne s’impute à un plan qui n’existe pas, et le mois suivant n’y change rien. Sans objet en déploiement client, où il n’y a rien à revendre. | { "error": "No plan assigned. Ask your administrator to assign one." } |
| 402 | Le plafond mensuel du plan est atteint. Sans objet en déploiement client. | { "error": "Plan limit reached" } |
| 503 | La passerelle n’a pas pu lire ce dont elle a besoin pour décider : la liste de révocation des sujets, celle des sessions d’agent, ou les rôles et la politique en vigueur. Elle refuse plutôt que de supposer : une politique installée pour restreindre, silencieusement remplacée par la plus large, serait une ouverture déguisée en tolérance de panne. | { "error": "Authorization decision could not be made, so the request was refused." } |
| 503 | L’accord était acquis, et le journal des décisions n’a pas pu l’enregistrer. Un accord que le journal n’a pas pris n’est pas un accord. | { "error": "Authorization decision could not be recorded, so the request was refused." } |
| 500 | L’endpoint désigne une variable d’environnement pour la clé du fournisseur, et cette variable est absente de l’environnement de la passerelle. C’est une panne de configuration côté exploitant, pas un défaut de droit. | { "error": "Missing env variable: <nom de la variable>" } |
| 502 | La passerelle n’a pas réussi à joindre le fournisseur : proxy d’entreprise injoignable, autorité de certification interne absente, certificat client refusé. 502 et non 500, parce que la panne est en aval. | { "error": "<le message de la couche de sortie réseau, qui nomme la variable à poser>" } |
| 503 | La base de facturation n’a pas répondu au moment de vérifier le plan et le plafond du compte. La passerelle refuse plutôt que de servir un appel qu’elle sait ne pas pouvoir compter, et elle refuse avant d’appeler le fournisseur, donc rien n’est payé. Ce refus n’existe que sur le profil serverless : en on-premise il n’y a rien à facturer. | { "error": "Usage cannot be metered right now, so the request was refused rather than served unbilled. Nothing was sent upstream." } |
| 503 | Le fournisseur a répondu, et la ligne de facturation de cette réponse n’a pas pu être écrite. La réponse est retenue plutôt que servie gratuitement : l’appel au fournisseur, lui, est déjà payé. Ne concerne que les réponses non diffusées : sur un flux, l’appelant a déjà tout reçu quand l’écriture échoue. | { "error": "This answer could not be metered, so it was not served. Nothing was billed for it either." } |
Les bornes de transport
Section intitulée « Les bornes de transport »Ce que l’appelant doit prévoir, et surtout ce sur quoi personne ne le protège :
| Borne | État |
|---|---|
| Plafond de durée d’une requête | Il existe. 1800 secondes par défaut, réglable par l’exploitant. |
| Limite de débit | Aucune. |
| Limite de taille du corps | Aucune. |
| Requête préalable CORS | Non traitée. |
| Identifiant de corrélation renvoyé | Aucun. La passerelle lit x-request-id pour son journal, elle ne le renvoie pas. |
Ces absences sont des informations contractuelles au même titre qu’un code d’erreur : qui construit sur cette passerelle doit savoir que rien ne le protège d’un appel trop gros ou trop fréquent.
La documentation hors ligne
Section intitulée « La documentation hors ligne »La passerelle sert la documentation du produit quand l’exploitant l’a installée. C’est ce qui la rend lisible sur un réseau fermé, où le site public est injoignable par construction.
Deux adresses, et un seul segment de chemin réservé :
| Route | Ce qu’elle fait |
|---|---|
ALL /v | La racine du segment que la documentation réserve. Elle redirige vers la version installée, ce qui donne une adresse d’entrée qui ne change pas quand le déploiement est mis à jour. Lecture seule et sans authentification, comme tout ce qui vit sous ce segment. |
ALL /v/* | Les pages, feuilles de style, index de recherche et fichiers pour agents de l’archive de documentation que l’exploitant a dépliée (D-33). Une adresse qui porte le numéro de la version installée sert le fichier ; une adresse qui n’en porte pas redirige vers la version installée ; un autre numéro est refusé. Lecture seule, sans authentification, et jamais de liste de répertoire. |
Trois propriétés à connaître avant d’écrire contre ces adresses :
- Aucune authentification n’est demandée, et une preuve présentée est sans effet. Les mêmes pages sont publiées sans compte sur le site public, et un navigateur ne peut pas présenter les en-têtes de preuve décrits plus haut.
- Le premier segment est réservé. Un endpoint de relais qui porterait ce nom serait masqué par ces deux routes, qui sont montées avant le relais.
- L’adresse sans numéro de version est stable. Une adresse qui ne porte pas de numéro redirige vers la version installée : elle reste juste après une mise à jour du déploiement, ce qui en fait la valeur à configurer plutôt que l’adresse portant le numéro.
Ce que la passerelle refuse sous ces adresses :
| Code | Quand | Corps |
|---|---|---|
| 501 | Aucune archive de documentation n’est déclarée sur ce déploiement. Le segment reste réservé quand même : un segment qui n’apparaîtrait qu’une fois l’archive dépliée laisserait marcher un point d’appel qui porte ce nom, jusqu’au jour où l’exploitant l’installe. | { "error": "This gateway serves no documentation: no directory is configured (LEMNISCATE_DOCS_DIR). Ask the operator of this deployment to unpack the documentation archive shipped with the server release and point the gateway at it." } |
| 405 | Une méthode autre que la lecture est employée sous le segment de documentation. | { "error": "Documentation is read-only: only GET and HEAD are served under this path." } |
| 404 | L’adresse vise un numéro de version qui n’est pas celui installé. Elle n’est pas redirigée vers celui qui l’est : rendre une autre version que celle demandée serait un mensonge invisible, puisque les pages se ressemblent. | { "error": "This gateway serves documentation version <numéro installé>, at <base>. Version <numéro demandé> is not installed here." } |
| 400 | Le chemin cherche à sortir de l’archive : un segment qui remonte, une barre encodée, un encodage invalide. | { "error": "Not a documentation path." } |
| 404 | Aucun fichier ne correspond à l’adresse sous la version installée, ou le répertoire visé n’a pas de page d’accueil : un répertoire ne rend jamais la liste de son contenu. | { "error": "No such documentation page." } |
Rien n’est servi tant que l’exploitant n’a pas posé LEMNISCATE_DOCS_DIR. Ce réglage est
décrit sur la page des variables d’environnement.
Interroger la documentation depuis un agent
Section intitulée « Interroger la documentation depuis un agent »Les deux fichiers pour machines de l’archive (llms.txt et llms-full.txt) se lisent
directement, sans numéro de version : /v/llms.txt et /v/llms-full.txt. Ils rendent le
corpus entier.
Pour une question ciblée, deux adresses de plus, sous le même segment réservé :
| Route | Ce qu’elle fait |
|---|---|
ALL /v/_search | Une question, et les passages de la documentation installée qui la couvrent, avec l’adresse de chaque page sur cette passerelle. Aucun modèle de langage n’est appelé : le service rend du texte qu’il a, jamais du texte qu’il fabrique, et quand rien ne couvre la question il rend une liste vide en le disant. Lecture seule et sans authentification, comme tout ce qui vit sous ce segment. |
ALL /v/_mcp | Le protocole par lequel un agent interroge la documentation installée : JSON-RPC 2.0, sans état, sans flux d’événements, un seul outil de recherche. Il ne fait que traduire la route de recherche ci-dessous, et rend la même donnée. Aucun modèle de langage n’est appelé. POST seulement ; une lecture reçoit un refus, ce serveur n’ouvrant aucun canal d’événements. |
Aucun modèle de langage n’est appelé, et c’est la propriété à connaître avant d’écrire contre ces adresses. Le service rend des passages de la documentation, jamais une réponse rédigée. Trois conséquences :
- ce qu’il rend est du texte publié, vérifiable en suivant l’adresse jointe à chaque passage ;
- il ne coûte rien par question, et rien ne sort de la machine qui l’héberge ;
- quand rien ne couvre la question, il rend une liste vide et le dit. Le champ
answeredvaut alorsfalseet un champnoteporte la phrase. Une liste vide est un fait sur la documentation, pas une panne du service.
GET /v/_search?q=comment+plafonner+la+depense+d+un+client&limit=3
{ "version": "1.0.0", "base": "/v/1.0.0/", "query": "comment plafonner la depense d un client", "terms": ["plafonner", "depense", "client"], "answered": true, "results": [ { "page": "Plafonner la dépense d'un client", "section": null, "url": "/v/1.0.0/guides/plafonner-la-depense/", "excerpt": "…", "score": 11.4, "coverage": 1 } ] }Le serveur MCP expose le même service sous le protocole que les clients d’agents savent
parler. Il est sans état : chaque message est indépendant, aucune session n’est ouverte,
et une lecture est refusée puisqu’il n’a aucun événement à pousser. Il implémente
initialize, ping, tools/list et tools/call, et offre un seul outil,
search_documentation, dont la donnée structurée est exactement la réponse ci-dessus.
Ce que ces deux adresses refusent :
| Code | Quand | Corps |
|---|---|---|
| 501 | Aucune archive de documentation n’est déclarée sur ce déploiement, donc il n’y a rien à interroger. Le chemin reste monté quand même, pour la même raison que le reste du segment : une adresse qui n’apparaîtrait qu’une fois l’archive dépliée cacherait la réservation jusqu’à ce jour-là. | { "error": "This gateway serves no documentation, so it cannot answer questions about it (LEMNISCATE_DOCS_DIR is not set). Ask the operator of this deployment to unpack the documentation archive shipped with the server release and point the gateway at it." } |
| 501 | L’archive dépliée ne porte pas llms-full.txt, le corpus pour machines dont l’index est tiré. Les pages continuent d’être servies : seule la recherche est indisponible. La passerelle ne refuse pas de démarrer pour autant : l’éteindre entière, donc le relais vers les modèles, pour un fichier de commodité serait hors de proportion. | { "error": "The documentation installed here carries no llms-full.txt, which is the machine-readable corpus this search reads. Pages are still served; only search is unavailable. That file sits at the root of every documentation archive, so the most likely cause is a partly unpacked archive." } |
| 405 | Une méthode autre que la lecture est employée sur la recherche. | { "error": "Documentation search is read-only: only GET and HEAD are served at this path." } |
| 400 | Aucune question n’accompagne l’appel, ou elle est vide. Le refus nomme le paramètre et en donne un exemple complet, plutôt que de rendre une liste vide qu’un appelant prendrait pour « la documentation ne sait pas ». | { "error": "Ask a question with the \"q\" query parameter, for example /v/_search?q=how+do+I+disable+telemetry. Add \"limit\" to ask for fewer or more passages." } |
| 405 | Une lecture est tentée sur le serveur MCP. Le protocole ouvre son canal d’événements par une lecture ; ce serveur n’a rien à pousser et le refuse, plutôt que d’ouvrir un canal muet qu’un client attendrait indéfiniment. | { "error": "This MCP endpoint answers POST only. It is stateless and opens no event stream. To read the same answers without MCP, use /v/_search?q=…" } |
| 400 | Le corps de la requête ne s’analyse pas comme du JSON. Une erreur de protocole sur un message bien formé, elle, se rend en JSON-RPC avec un statut 200 : ce n’est pas le transport qui a échoué. | { "error": "This MCP endpoint expects a single JSON-RPC 2.0 message as the request body." } |
Il n’existe pas de description OpenAPI, et c’est délibéré
Section intitulée « Il n’existe pas de description OpenAPI, et c’est délibéré »Le relais ne définit pas d’interface à lui : il concatène l’adresse de l’appelant derrière celle du fournisseur. Un fichier OpenAPI ne pourrait donc décrire que la route fourre-tout ci-dessus (dont aucun client utile ne se génère) ou bien recopier l’interface du fournisseur, qui n’est pas la nôtre et se périmerait ici à chaque évolution chez lui.
Il annoncerait surtout une forme de requête que la passerelle ne vérifie pas : elle ne valide ni schéma, ni type de contenu, ni taille. Ce qu’il faut réellement savoir pour écrire contre elle est la règle de concaténation et la table des refus, et c’est ce que cette page donne.