# La prose, déclarée dans la config.

> Déclarer des guides Markdown dans la config ApiGlow : callouts, onglets de code, cartes d'opération, interpolation {{var}}, frises de versions, avis.
>
> Source: https://apiglow.dev/fr/docs/docs-pages/index.md


`docsPages` tisse guides, tutoriels et changelogs dans la navigation, au-dessus de la référence API, rendus dans le navigateur comme tout le reste. La [page de fonctionnalité](/fr/features/portal/) plante le décor ; ce guide est la syntaxe complète.

## Trois sortes d'entrées

`docsPages` est un tableau ordonné, et **l'ordre du tableau est l'ordre de la nav**. Il n'y a pas de champ `position`. Les entrées se distinguent par leurs clés :

```json {title="api-doc-config"}
{
  "docsPages": [
    { "slug": "getting-started", "title": "Bien démarrer",
      "url": "/docs-pages/getting-started.md", "home": true },
    { "group": "Guides", "collapsed": false, "pages": [
      { "slug": "pagination", "title": "Pagination", "url": "/docs-pages/pagination.md" },
      { "title": "Page de statut", "href": "https://status.example.com" }
    ]},
    { "title": "GitHub", "href": "https://github.com/acme/api" }
  ]
}
```

- **Une page** — `slug` plus exactement une source de contenu (voir la section suivante). Éventuellement `kind: "changelog"`, qui l'inscrit dans le traitement en frise.
- **Un groupe** — un seul niveau, repliable, avec `collapsed: true` pour démarrer fermé. Un groupe contient des pages et des liens externes, jamais d'autres groupes. `id` est optionnel : il sert à l'identité de fusion en multi-spec et prend par défaut le titre slugifié.
- **Un lien externe** — `href` au lieu d'un contenu, rendu avec une icône « externe » et ouvert dans un nouvel onglet.

Une entrée invalide — page sans slug ni contenu, groupe sans `pages`, lien sans `href` — est écartée avec un avertissement console.

## Deux zones de nav : la doc au-dessus, l'annexe en dessous

Par défaut, chaque entrée vit au-dessus de la référence API, sous le titre « Documentation ». Une entrée de premier niveau — page, groupe ou lien externe — peut déclarer `nav: "bottom"` et rejoindre une zone finale au pied de la barre latérale, sous toute la référence et les webhooks.

```json {title="api-doc-config"}
{
  "docsPages": [
    { "slug": "getting-started", "title": "Bien démarrer", "url": "/docs/start.md" },
    { "slug": "support", "title": "Support", "url": "/docs/support.md", "nav": "bottom" },
    { "group": "Mentions légales", "nav": "bottom", "pages": [ … ] },
    { "title": "Statut", "href": "https://status.example.com", "nav": "bottom" }
  ]
}
```

La zone finale ne porte aucun titre, seulement un séparateur : elle se lit comme une annexe plutôt que comme une seconde section de doc.

`nav` est un choix de premier niveau. Dans un groupe, il est ignoré avec un avertissement console, parce qu'un groupe voyage entier.

La zone est un ordre autant qu'une place : le pager précédent/suivant, la recherche et les exports parcourent la zone du haut d'abord, puis la référence, puis la zone du bas. Une page déclarée en premier mais envoyée en bas est la dernière que le pager atteint.

## D'où vient le contenu d'une page

Déclarez exactement une clé parmi trois. S'il s'en glisse plusieurs, ce que la page **transporte** gagne sur ce qu'elle devrait aller chercher.

| Clé | Contenu | Usage typique |
|---|---|---|
| `url` | Un fichier à récupérer — `.md` par défaut, `.html` et `.txt` reconnus à l'extension | Le défaut : des fichiers à côté d'`index.html` |
| `content` | Le texte lui-même, dans la config | Un backend qui génère la config génère la prose avec |
| `contentId` | L'id d'un élément de la page hôte contenant le texte | De la prose écrite à la main derrière un login, ou un hôte qui ne sert qu'une route |

Le réceptacle de `contentId` doit être un élément script au **type non exécutable**. Un `<script>` nu exécuterait son contenu comme du JavaScript avant que l'app le lise.

```html {title="index.html"}
<script type="text/markdown" id="doc-pagination">
  # Pagination

  Les endpoints de collection renvoient une page à la fois. Commencez par
  [créer un animal](apidoc:createPet).
</script>
```

Deux mécaniques à connaître. Un `</script>` littéral dans un exemple de code ferme l'élément trop tôt : cette page-là relève d'un fichier ou de `content`. Et l'indentation commune à toutes les lignes est retirée, donc le bloc peut rester indenté dans votre markup sans que Markdown lise quatre espaces comme un bloc de code.

Un `contentId` qui ne pointe sur rien échoue comme un fichier injoignable : une erreur visible qui nomme l'id, pas une page blanche.

**Formats.** Un contenu transporté n'a pas d'extension : `format` (`"markdown"`, `"html"` ou `"text"`) le dit quand il le faut, et gagne sur l'extension comme sur le type de l'élément. Les pages HTML passent par le même assainisseur que tout le reste et ignorent les enrichissements réservés au Markdown. L'assainisseur retire un élément `<style>`, qui restylerait toute la documentation, et les contrôles de formulaire où l'on pourrait inviter un lecteur à taper quelque chose — leur texte est conservé. Un attribut `style` en ligne fonctionne toujours. Un `.txt` se rend préformaté, sans sommaire mais avec sa pagination. Un bloc de frontmatter YAML en tête d'un fichier Markdown est retiré et ignoré, donc un README écrit pour un autre outil se rend proprement.

## Ou pointer un manifeste

`docsPages` peut être une URL au lieu d'un tableau :

```json
{ "docsPages": "/docs-pages/manifest.json" }
```

Le manifeste est `{ "pages": [ … ] }` avec les mêmes entrées, et **les URL relatives qu'il contient se résolvent contre le manifeste lui-même**, si bien que le dossier de doc peut être versionné et déplacé indépendamment de la page hôte. Un manifeste qui ne charge pas ou ne parse pas affiche une erreur visible au niveau de la nav et laisse la référence intacte.

## Langues et prise de contrôle de l'accueil

`title`, sur toutes les sortes d'entrées, et chaque champ de contenu d'une page acceptent une table par langue — `{ "en": "/guide.en.md", "fr": "/guide.fr.md" }` — résolue selon la langue courante de l'UI, puis `en`, puis la première clé déclarée. Changer de langue re-rend la page ouverte et les libellés de nav. Une chaîne simple signifie « pareil dans toutes les langues ».

`home: true` sur une page au plus en fait la vue d'atterrissage : `#/` rend cette page, et la vue d'accueil technique (exports, scénarios épinglés, statistiques) déménage vers `#/overview` avec sa propre entrée de nav auto-générée. Déclaré deux fois, le premier gagne, avec un avertissement console.

## Écrire des pages qui répondent

Les pages Markdown reçoivent ces enrichissements par-dessus le GFM, chacun dégradant proprement dans tout autre moteur de rendu. Trois conteneurs — cartes, étapes et onglets — ont [leur propre section](#cartes-étapes-et-onglets), tout comme l'[interpolation](#des-pages-qui-se-lisent-avec-les-valeurs-du-lecteur) `{{var}}`.

- **Les callouts** — `> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]` et `[!CAUTION]` en première ligne d'une citation deviennent de vraies alertes stylées. C'est exactement la syntaxe que GitHub rend.
- **Les onglets de code** — des blocs adjacents sans ligne vide entre eux deviennent un seul bloc à onglets. Le libellé vient de la méta du fence (` ```js Node.js `) ou du nom du langage. Le choix du lecteur est mémorisé et appliqué à chaque groupe d'onglets de chaque page. Des blocs séparés par une ligne vide restent indépendants : la fonctionnalité s'active par adjacence.
- **Les références d'opérations** — un lien au schéma `apidoc:` se rend en ligne avec le badge de méthode de l'opération, adressée par `operationId` ou par `"METHOD /path"` :

  ```markdown
  Voir [créer un animal](apidoc:createPet) ou [lister](apidoc:GET /pets).
  ```

  Les **cartes** d'opérations viennent d'un bloc dont la chaîne d'info est `apidoc:operation`, une référence par ligne. Chacune rend méthode, chemin, résumé et badge de dépréciation le cas échéant, la carte entière étant un lien vers l'opération :

  ````markdown
  ```apidoc:operation
  GET /pets/{petId}
  createPet
  ```
  ````

  Une référence qui ne résout rien se rend visiblement cassée — barrée, avec une infobulle — plutôt qu'en lien mort. Les cartes sont des liens vers la référence, pas des consoles embarquées : envoyer des requêtes reste le rôle du [panneau d'essai](/fr/docs/try-it/). Les références visent la spec active uniquement ; il n'y a pas de référence inter-spec.
- **Références de page** — `apidoc:page/{slug}` relie une page de doc à une autre. Elle se rend comme un lien ordinaire, construit par le routeur : il continue de fonctionner dans une installation [multi-spec](/fr/docs/multi-spec/), là où un `#/page/…` écrit à la main perdrait le préfixe de la spec. Un slug inconnu reçoit la même marque de lien cassé qu'une opération inconnue :

  ```markdown
  Voir la [pagination](apidoc:page/pagination).
  ```

## Cartes, étapes et onglets

Trois conteneurs couvrent ce pour quoi on fait d'habitude venir MDX — des composants dans la prose — sans rien compiler ni livrer de framework. Vous les écrivez comme du MDX. Ce sont de la syntaxe Markdown, lue par le propre parseur de l'app, et un moteur de rendu qui ne les connaît pas (GitHub, votre éditeur) retire les balises et montre la prose qu'elles contiennent.

````markdown
<Cards>

<Card title="Démarrage rapide" href="apidoc:page/getting-started">
Votre première requête, en deux minutes.
</Card>

<Card title="Créer un animal" href="apidoc:createPet">
L'appel par lequel commencent la plupart des intégrations.
</Card>

</Cards>

<Steps>

<Step title="Installer">
```bash
npm i
```
</Step>

<Step title="Appeler">
Envoyez votre première requête depuis la console d'essai.
</Step>

</Steps>

<Tabs>

<Tab label="Cloud">
Rien à installer.
</Tab>

<Tab label="Auto-hébergé">
Lancez le conteneur à côté de votre API.
</Tab>

</Tabs>
````

- **`<Cards>`** rend une grille de liens. `<Card>` exige un `title` et un `href`, qui peut être une référence `apidoc:` vers une opération ou une page.
- **`<Steps>`** rend une liste numérotée. Le titre d'un `<Step>` est facultatif, et c'est un libellé, pas un titre de section : il reste hors du sommaire. Écrivez un vrai `###` dans l'étape si vous voulez l'y voir.
- **`<Tabs>`** rend la même liste d'onglets que les onglets de code, clavier compris. `<Tab>` exige un `label`. Les onglets de prose ne se synchronisent pas et ne retiennent rien — « Cloud ou auto-hébergé » n'a rien à suivre ailleurs.

Les règles, toutes. Les balises prennent une majuscule et s'écrivent exactement. Les attributs s'écrivent `nom="valeur"`, guillemets doubles uniquement. Le corps d'un enfant est du Markdown complet — blocs de code, tableaux, encadrés, `{{var}}` — et les lignes vides autour des balises sont ce qui permet à un moteur ordinaire de le lire. Les conteneurs ne s'imbriquent pas, et ce qui traîne entre deux enfants sans en être un est ignoré. Un conteneur mal formé n'est pas reconnu du tout : la prose qu'il contient survit comme du texte ordinaire. Écrit dans un bloc de code, un conteneur se rend tel quel, et c'est ainsi que cette page les montre.

Ce qui manque à dessein : MDX lui-même, les expressions JSX, `import`, et les composants maison. Chacun supposerait de compiler et d'exécuter du code dans le navigateur du lecteur, ce que l'app ne fait jamais.

## Des pages qui se lisent avec les valeurs du lecteur

Les pages Markdown résolvent `{{var}}` depuis la même composition que la console d'essai : l'environnement sélectionné, avec les [identifiants fournis par l'hôte](/fr/docs/auth/#quand-la-page-hôte-a-déjà-une-session) en dessous. La page se rend donc avec l'URL de base du lecteur et son identifiant de tenant, et le bouton de copie d'un bloc de code copie le texte interpolé.

Il y a trois issues :

- **Une valeur résolue et non sensible** se rend en texte brut — ni pastille, ni surlignage, pour qu'un extrait de code se lise comme un extrait de code.
- **Une valeur sensible ne se rend jamais.** Vous obtenez une pastille masquée portant le nom et `••••`, et la valeur n'entre ni dans le DOM, ni dans le presse-papiers, ni dans l'index de recherche, ni dans aucun export. Elle n'est pas caviardée après coup : elle n'est jamais émise. Tout identifiant fourni par l'hôte est sensible par définition, donc couvert aussi.
- **Une valeur manquante** se rend en pastille d'avertissement portant le nom, sur le même principe que le blocage d'envoi de la console d'essai. Cette pastille est un bouton qui ouvre le gestionnaire d'environnements. Sous `environmentsLocked`, la pastille reste et l'offre disparaît.

Copier une pastille donne `{{name}}`, le gabarit qu'elle représente, plutôt qu'un nom de variable nu qui aurait l'air résolu. Une valeur vide compte comme manquante, exactement comme dans la console d'essai.

Pour écrire *à propos* d'une variable plutôt que d'en résoudre une, échappez-la par une barre oblique inverse. Dans un bloc ou un span de code, les barres obliques inverses sont littérales : tapez `\{{name}}`. En prose, le markdown en mange une : tapez `\\{{name}}`.

Trois surfaces restent non interpolées :

- **Les pages `.html` et `.txt`** — l'interpolation est un enrichissement du Markdown, comme les callouts et les onglets de code.
- **L'index de recherche** — il est construit une fois depuis la source. Indexer les valeurs rendrait les résultats dépendants de l'environnement choisi, et indexerait des secrets.
- **`llms.txt` et `llms-full.txt`** — ce sont des exports machine de la documentation publiée, pas de la session d'un lecteur.

Deux détails pour les auteurs. Les titres s'interpolent aussi, mais leurs ancres sont calculées depuis la source avant que les valeurs n'arrivent, donc un lien profond ne dépend jamais de qui lit. Et les valeurs sont insérées comme du texte, jamais comme du balisage, si bien qu'une URL de base pleine de tirets bas ne peut pas transformer la moitié de votre paragraphe en emphase.

## Les pages de changelog

Une page qui déclare `kind: "changelog"` se rend en frise de versions : un point dans la gouttière de gauche par `h2`, avec une ligne continue qui les relie.

```json {title="api-doc-config"}
{ "slug": "changelog", "title": "Journal des versions",
  "url": "/docs-pages/changelog.md", "kind": "changelog" }
```

La convention est d'un `h2` par version, avec la date dans le texte du titre — `## 1.2.0 — 2026-05-01`. Rien ne parse cette date, donc rien n'a besoin de s'entendre sur une locale, et le même fichier reste un changelog markdown ordinaire dans GitHub ou n'importe quelle autre visionneuse.

Le traitement s'accroche au contenu rendu : il vaut pour le `.md` comme pour le `.html`. Un `.txt` n'a pas de `h2` à décorer et ne reçoit rien. Un `kind` inconnu est écarté avec un avertissement console, comme un `format` inconnu.

## « Cette page vous a-t-elle été utile ? »

Posez `feedback.url` et chaque page de prose gagne une ligne de verdict, entre le contenu et la pagination :

```json {title="api-doc-config"}
{ "feedback": { "url": "https://example.com/docs-feedback" } }
```

Un clic envoie ceci en POST, en JSON, à **votre** endpoint. Il n'y a pas d'autre destinataire, et aucun endpoint à nous nulle part dans le produit :

```json {title="Corps du POST"}
{ "page": "pagination", "verdict": "up" }
```

`verdict` vaut `"up"` ou `"down"`. `page` est le **slug** de l'entrée, pas son URL : la route est l'identité d'une page, là où les fichiers et les contenus transportés bougent en dessous. Sans `feedback.url` déclaré, il n'y a pas de ligne du tout — le défaut est `null`, et l'app n'envoie jamais rien nulle part d'elle-même.

La ligne répond. En cas de succès, les boutons cèdent la place à un remerciement : un verdict par rendu, pas de compteur de votes, rien de stocké. En cas d'échec, elle le dit et reste cliquable. Les deux issues passent par la zone d'annonce vocale.

Deux choses à prévoir. Le POST porte un en-tête `content-type: application/json`, ce qui en fait une requête cross-origin **préliminaire** (`OPTIONS`) dès que l'endpoint n'est pas sur la même origine : votre serveur doit y répondre aussi, la même négociation que réclame la [console d'essai](/fr/docs/cors/). Et en [multi-spec](/fr/docs/multi-spec/), `feedback` se surcharge par spec : une API peut récolter des verdicts pendant que sa voisine n'en récolte aucun, ou chacune poster vers son propre collecteur.

## Confort, recherche, exports

Chaque page reçoit un sommaire construit sur ses `h2`/`h3`, où la section en cours de lecture se surligne au défilement, plus une pagination précédent/suivant dans l'ordre aplati et zoné de la nav.

Le **contenu** des pages est indexé dans la palette Cmd+K, paresseusement, à la première ouverture de la palette, et un résultat pointe directement le bon titre de section.

La même prose alimente `llms.txt` — pages de la zone du haut sous le titre de leur groupe en `## Guides`, celles du bas en `## Resources` — et `llms-full.txt`, contenus inclus. Voir [llms.txt et MCP](/fr/docs/ai/).

En [multi-spec](/fr/docs/multi-spec/), les pages fusionnent par identité : slug pour les pages, id pour les groupes, href pour les liens. Une entrée de spec remplace sur place l'entrée racine correspondante, et les autres s'ajoutent après. Des pages racine partagées par toutes les specs sont possibles ici, contrairement aux scénarios. `home` se résout par spec, donc une API peut prendre le contrôle de son accueil pendant que sa voisine garde la vue classique.

La seule chose que le navigateur du lecteur stocke pour tout cela est le langage d'onglet de code sélectionné.

