FR EN

Chapitre 21Référence

Référence des routes

Les quatre routes : requêtes, réponses, codes, plafonds. Ce qu'elles acceptent, ce qu'elles refusent, et dans quel ordre.

5 min de lecture10 sectionsChapitre 21 / 22

Ce qui est commun aux quatre

  • Réponses en JSON, cache-control: no-store.
  • Toute méthode non prévue reçoit 405.
  • L'ordre des contrôles est le même partout : débit → taille annoncée → identité → taille reçue → forme → règles métier.
  • Les messages d'erreur ne portent aucun détail technique. Le détail va dans les journaux serveur.
  • Aucune réponse ne contient de secret : ni jeton, ni empreinte, ni contenu de session.

POST /api/auth

Ouvre une session à partir de la clé du site. C'est la seule route qui évalue une identité.

requête bash
curl -i -X POST https://le-site.fr/api/auth \
  -H "content-type: application/json" \
  -d '{"key":"la-cle-du-site"}'
réponse — 200 text
set-cookie: inline_session=…; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=28800
set-cookie: inline_edit=1; Secure; SameSite=Strict; Path=/; Max-Age=28800

{ "ok": true }
CodeCorpsQuand
200{ "ok": true }Clé correcte. Deux cookies posés.
401{ "error": "clé incorrecte" }Clé fausse, absente, trop longue, corps illisible, ou configuration manquante. Le même message dans tous les cas.
429{ "error": "Trop de tentatives…" }Plus de 5 tentatives par quart d'heure. En-tête retry-after.
405{ "error": "method_not_allowed" }Autre méthode que POST ou DELETE.

Coût : environ 350 ms, le temps d'argon2id. C'est voulu — c'est ce qui rend la force brute impraticable. Corps limité à 2 000 octets, clé à 256 caractères : au-delà, rien n'est calculé.

DELETE /api/auth

Ferme la session : les deux cookies sont effacés. Répond toujours 200.

GET /api/content

Lit l'état réel du dépôt et la version de référence du verrou optimiste. L'overlay l'appelle au chargement.

requête bash
curl -s "https://le-site.fr/api/content?path=src/content/pages/fr/home.json" \
  --cookie "inline_session=…"
réponse — 200 json
{
  "content": "{\n  \"meta\": { … }\n}\n",
  "version": "3f2a1c…"
}
CodeQuand
200Lecture réussie. content est le fichier entier, en texte.
400path absent ou hors de la liste blanche.
401Pas de session valide.
404Le fichier n'existe pas dans le dépôt.
429Plus de 60 lectures par 5 minutes.
502Le dépôt est injoignable ou refuse l'accès.

POST /api/save

Valide et publie une page. C'est la route la plus contrôlée du projet.

corps attendu json
{
  "path": "src/content/pages/fr/home.json",
  "content": "{ … le fichier JSON entier, en texte … }",
  "version": "3f2a1c…",
  "message": "content(fr): home — hero.title"
}
ChampContrainte
pathsrc/content/pages/{langue déclarée}/{page}.json, 120 caractères au plus.
contentLe fichier entier, pas un diff. 100 Ko au plus.
versionCelle reçue de /api/content. Vide pour un fichier neuf. 200 caractères au plus.
messageFacultatif. Réduit à une ligne de 120 caractères, sans caractère de contrôle.
réponse — 200 json
{ "version": "9b7e42…" }
CodeCorpsQuand
200{ "version": … }Écrit. La nouvelle version sert à la publication suivante.
400bad_requestCorps illisible, chemin hors liste blanche, champ manquant.
401unauthorizedPas de session valide.
409conflictLa version ne correspond plus : quelqu'un a publié entre-temps.
413too_largeContenu > 100 Ko, ou enveloppe > 128 Ko.
422invalid_contentSchéma Zod, média invalide, identifiants en double, balisage hostile.
429message d'attentePlus de 30 publications par 5 minutes.
502not_found, unauthorized, unavailableLe dépôt a refusé ou n'a pas répondu.
Ce qui est écrit

Exactement ce qui vient d'être validé et assaini, réécrit en JSON indenté. On repart de l'objet analysé et non du résultat de Zod : celui-ci supprimerait au passage toute clé qu'il ne connaît pas encore.

POST /api/upload

Reçoit une image déjà recadrée et convertie par le navigateur, la vérifie, la renomme et la range dans le dépôt.

corps — multipart/form-data text
file   le fichier image (JPEG, PNG ou WebP), 20 Mo au plus
name   nom souhaité, 200 caractères au plus — indicatif, toujours réécrit
réponse — 200 json
{ "src": "fournil-au-petit-matin.webp", "width": 1600, "height": 900 }
CodeCorpsQuand
200{ src, width, height }Rangé. src est le nom réécrit, à recopier tel quel dans le contenu.
400bad_requestFormulaire illisible, champ file absent, ou nom refusé après réécriture.
401unauthorizedPas de session valide.
413too_largePlus de 20 Mo, annoncés ou reçus.
415unsupported_format + kindFormat non reconnu aux octets, ou dimensions hors bornes (1 à 10 000 px). kind dit ce qui a été reconnu.
429message d'attentePlus de 30 envois par quart d'heure.
502code du dépôtÉcriture impossible.
Rien de ce qui est déclaré n'est cru

Ni le type MIME annoncé, ni le nom du fichier, ni les dimensions. Le format est reconnu à ses octets, les dimensions sont lues dans l'en-tête, le nom est réécrit — puis revérifié contre la même liste blanche que celle imposée aux références dans le contenu.

Le routeur

Un site déclare ses routes une fois, et l'hébergeur s'y branche. La déclaration tient en une ligne : la seule décision qui appartienne au site, ce sont ses langues.

src/lib/api.ts ts
import { createRouter } from 'inline-core/server';
import { LOCALES } from './locales';

export const api = createRouter({ locales: LOCALES });

createRouter renvoie les quatre routes sous trois formes. Chaque hébergeur prend celle qui lui convient — aucune n'est privilégiée, et le paquet n'en connaît aucun.

FormePour quel hébergeur
api.routes['/api/save']celui qui découvre les routes par l'arborescence et attend des exports nommés
api.handle(request, env)tous les autres : un point d'entrée unique, qui choisit la route et la méthode
api.find(pathname)la route servant un chemin, ou undefined — de quoi séparer l'API du statique dans un serveur maison

env porte ce que l'hébergeur expose : les variables, et les liaisons éventuelles — dont RATE_LIMIT. Une méthode non servie reçoit un 405, un chemin inconnu un 404, et toujours en JSON : l'appelant est l'overlay, une page d'erreur ne lui apprend rien.

Depuis la version 2.1.0

Avant, chaque site rangeait les quatre fabriques dans quatre fichiers écrits à la convention d'un hébergeur précis. La répartition — quel chemin, quelle méthode, quelle réponse — vit désormais dans le paquet et se met à jour avec lui.

Les fabriques

Elles restent exportées, pour un câblage à la main : createRouter ne fait que les réunir. Un site en 2.0.x n'a donc rien à changer.

ts ts
import {
  createAuthRoute,      // ()                     → onRequest, onRequestPost, onRequestDelete
  createContentRoute,   // ({ locales })          → onRequest, onRequestGet
  createSaveRoute,      // ({ locales })          → onRequest, onRequestPost
  createUploadRoute,    // ()                     → onRequest, onRequestPost
} from 'inline-core/server';

SiteConfig ne contient qu'une chose : locales. Si cette interface s'allonge, c'est le signe qu'une décision du paquet a fuité vers les sites.

Les budgets de débit

RouteBudgetFenêtreCe qu'il protège
/api/auth515 minla clé du site
/api/content605 minle quota de l'API du dépôt
/api/save305 minle dépôt, le quota
/api/upload3015 minle dépôt, le quota

Les plafonds

PlafondValeur
Contenu d'une page100 000 octets
Enveloppe d'une requête JSON128 000 octets
Corps de /api/auth2 000 octets
Longueur d'une clé256 caractères
Fichier envoyé20 Mo
Dimensions d'image1 à 10 000 px
Longueur d'un chemin120 caractères
Message de publication120 caractères, une ligne
Durée de session8 heures