#API HTTP
Le CLI n’a aucun privilège : il appelle la même API que n’importe quel script.
Cette section documente cette API — ses deux modes d’authentification, ses
quatre familles de routes, et chacun de ses codes d’erreur. Tout ce que
node scripts/theme.mjs pousser sait faire, une boucle curl de six lignes
sait le faire aussi.
#L’adresse de base
Une seule, et elle n’est pas celle de cette page :
https://cms.webcosa.comSur dev.webcosa.com, /api/themes désigne la page qui documente la
route, pas la route. Les deux vivent dans la même application Next et ne se
distinguent que par le nom d’hôte. Tout exemple de cette section porte donc
l’adresse absolue du CMS : un chemin relatif copié depuis ici irait chercher de
la documentation et rendrait du HTML.
#Un premier appel
Il faut une clé d’API, créée depuis le CMS — Réglages → Clés d’API. Elle
commence par wc_ et ne s’affiche qu’une fois. Voir Authentification
pour ce que la clé désigne exactement, et Clés pour la créer par
l’API plutôt qu’à la souris.
curl -s https://cms.webcosa.com/api/themes \-H "Authorization: Bearer $WEBCOSA_CLE"{
"site": { "id": "9f2c…", "slug": "atelier-durand", "nom": "Atelier Durand" },
"themes": [
{ "id": "11111111-1111-4111-8111-111111111111", "nom": "Origo", "version": "1.0.0",
"role": "publie", "origine": "origo", "maj_le": "2026-08-05T09:12:44.108Z" },
{ "id": "22222222-2222-4222-8222-222222222222", "nom": "Origo (copie)", "version": "1.0.0",
"role": "brouillon", "origine": "origo", "maj_le": "2026-08-04T17:02:11.740Z" }
]
}La réponse contient le site résolu en plus des thèmes, et ce n’est pas du
confort : sans en-tête de désignation, la clé retient le premier site
accessible à son porteur, et cet ordre bouge le jour où un site s’ajoute au
compte. Le champ site dit sur quoi on vient de travailler.
#Ce que l’API permet
En un tableau, les neuf opérations existantes :
| Méthode | Chemin | Ce qu’elle fait | Portée exigée |
|---|---|---|---|
GET | /api/themes | Les thèmes du site, et le site résolu | lecture |
POST | /api/themes | Installer un thème du catalogue, ou dupliquer | themes |
PATCH | /api/themes | Renommer, publier | themes |
DELETE | /api/themes?id= | Retirer un thème brouillon | themes |
GET | /api/themes/<id>/fichiers | L’arborescence, ou un fichier | lecture |
PUT | /api/themes/<id>/fichiers | Écrire un fichier | themes |
DELETE | /api/themes/<id>/fichiers?chemin= | Supprimer un fichier | themes |
GET | /api/themes/<id>/export | Le thème entier, en un JSON | lecture |
POST | /api/themes/importer | Créer un thème depuis une archive | themes |
Les trois opérations sur /api/cles sont à part : elles refusent les clés
d’API et exigent une session de navigateur. La raison tient en une phrase — une
clé volée ne doit pas pouvoir s’en fabriquer d’autres et survivre à sa propre
révocation. Elle est développée dans Clés.
#Ce que l’API ne permet pas
L’application expose une cinquantaine de routes sous /api/ : pages, articles,
contacts, menus, domaines, facturation. Une seule famille est ouverte aux clés
d’API, /api/themes et ses sous-chemins ; le proxy la nomme explicitement et
refuse toutes les autres avec un 401 avant même que la route soit atteinte.
Ce n’est pas un oubli. Une route ajoutée demain sous /api/ reste fermée par
défaut, et l’ouvrir est un geste délibéré dans une liste courte qu’on relit — au
lieu d’être l’effet de bord du chemin qu’on lui a choisi.
Trois autres absences, à connaître avant de concevoir une intégration :
- Pas de webhooks. Rien ne te prévient qu’un thème a changé. Le CLI reconstruit l’information en comparant les empreintes de l’arborescence.
- Pas de pagination. Les listes rendent tout. Un site a quelques thèmes, un thème a au plus 120 fichiers : ce plafond-là est le seul qui compte.
- Pas de rendu. Aucune route ne rend du Liquid à la demande. L’aperçu d’un thème, c’est le site lui-même — voir La boucle locale.
#Le format
JSON dans les deux sens, UTF-8. Une requête qui porte un corps déclare
Content-Type: application/json ; un corps illisible n’est pas une exception,
il vaut 400 invalid_input comme un corps absent.
Les identifiants sont des uuid. Ceux des thèmes voyagent dans le chemin
(/api/themes/<id>/fichiers) ou en paramètre de requête (?id=), jamais dans le
corps — sauf PATCH /api/themes, qui prend le sien dans le corps parce qu’il
l’accompagne des champs à modifier.
Une erreur est un objet à un ou deux champs :
{
"error": "theme_publie",
"message": "Ce thème est celui que voient tes visiteurs. Publie-en un autre avant de le retirer."
}error est un code stable, fait pour être comparé en code. message est une
phrase rédigée en français, faite pour être affichée — elle n’est présente que
sur les refus où le code seul ne suffirait pas à savoir quoi faire. Le catalogue
complet est dans Codes d’erreur.
Traitez error et jamais le statut seul. 400 couvre à lui seul un chemin hors
dossier, une extension refusée, une erreur de syntaxe Liquid et un plafond de
fichiers atteint — quatre situations qui n’appellent pas la même réaction.
#Ce qui est compté
Un compteur, un seul, et il ne porte pas sur les appels réussis : dix clés
invalides par heure, comptées par empreinte du secret présenté. Au-delà,
429 trop_de_tentatives. Il n’existe aucune limite de débit sur les requêtes
authentifiées — voir Codes d’erreur pour ce que ce compteur
arrête réellement, et ce qu’il n’arrête pas.

