Utiliser l'API
URL de base, authentification, erreurs et client généré — ce qui vaut pour toutes les routes de l'API REST du CMS headless Spunto.
L'API est du REST sur JSON. Chaque route est décrite dans la
référence, générée à partir de la spécification servie à
/openapi.json.
Cette page couvre ce qui vaut pour toutes.
URL de base
Sur cms.spunto.net, une seule adresse publique sert tout le produit et route
/api/* vers l'API :
https://cms.spunto.net/apiLe préfixe est retiré avant que l'API ne voie la requête : une route documentée
comme /workspaces s'appelle donc à https://cms.spunto.net/api/workspaces.
La spécification elle-même est servie à la racine, /openapi.json, sans le
préfixe — c'est la seule exception, et elle existe pour que la page de référence
puisse la charger.
Si vous l'hébergez vous-même
Le préfixe /api appartient au proxy, pas à l'API. Lancez la stack en local et
il n'y a pas de proxy : l'API écoute sans préfixe, sur son propre port, donc
chaque exemple ici devient http://localhost:3001/workspaces/… — retirez le
/api, changez l'hôte.
Deux exceptions répondent sur le port du front dans tous les environnements,
parce qu'une adresse documentée qui ne répond pas là où vous travaillez est une
adresse fausse : /api/docs et /openapi.json. Elles sont relayées, et rien
d'autre sous /api ne l'est.
S'authentifier
Toutes les routes, sauf une poignée de portes d'entrée, exigent un jeton bearer :
Authorization: Bearer spk_...Trois sortes de jetons passent, et une fois la porte franchie rien ne les distingue :
| Jeton | Qui | Portée |
|---|---|---|
| une session | une personne connectée | ses espaces de travail, son rôle dans chacun, 30 jours |
spk_… | une clé API | un espace de travail, un rôle fixe, jusqu'à révocation |
spa_… | une app installée | un espace de travail, un rôle et des scopes |
Utilisez une clé API pour tout ce qui tourne sans surveillance. Créez-en une dans les réglages d'un espace de travail. Le secret n'est renvoyé qu'une fois, à la création — seul son SHA-256 est conservé.
L'espace de travail et le rôle d'une clé sont fixés à l'émission. Ils ne suivent pas l'appartenance de la personne qui l'a créée, et c'est tout l'intérêt : une clé ne doit ni gagner ni perdre de pouvoir parce qu'une personne a changé de poste.
Une clé API ne peut gérer ni les clés, ni les invitations, ni les membres, ni les installations d'apps. Tout cela exige une session humaine.
Ce qui est public
Neuf routes répondent sans jeton, pas une de plus : le contrôle de santé, la spécification, la page de référence, la politique d'inscription, l'aperçu d'une invitation, l'inscription, la connexion, l'échange de la connexion Google et le point d'accès de jeton OAuth.
Cette liste n'est pas tenue à jour en relisant le code et en espérant. Un test balaie toutes les routes de la spécification et exige un 401, avec les exceptions nommées une par ligne et justifiées. Une nouvelle route est protégée le jour même où elle est écrite, parce que les gardes sont montées sur le préfixe du chemin et non route par route.
Erreurs
Les erreurs sont du JSON, de la forme { "error": "..." }, et c'est le statut
HTTP qui porte le sens :
| Statut | Signifie |
|---|---|
400 | La requête est mal formée, ou une valeur a échoué à la validation |
401 | Pas de bearer, ou un bearer qui n'est plus valide |
404 | Cela n'existe pas — ou ce n'est pas à vous |
409 | La requête entre en conflit avec l'état actuel |
Le 404 a une double fonction, et c'est voulu
Il n'y a pas de 403 pour « vous n'êtes pas membre de cet espace de travail ». Un 403 confirmerait l'existence de l'espace à qui énumère des identifiants. L'API ne vous dira pas lequel des deux cas vous avez rencontré.
Pagination
Les listes se paginent par curseur keyset. Une réponse paginée porte
nextCursor ; renvoyez-le comme cursor pour obtenir la page suivante. null
signifie que vous êtes au bout.
Il n'y a ni OFFSET ni numéro de page, nulle part, et c'est une décision
tranchée plutôt qu'un oubli — voir
les vues sauvegardées.
Les curseurs sont opaques. Ne les analysez pas, ne les construisez pas, et n'en réutilisez pas un après un changement d'ordre de tri.
Le journal d'événements est la seule exception : son curseur est un identifiant d'événement, lisible à dessein.
Dates
Toutes les dates que renvoie l'API sont des chaînes ISO 8601. Pas de timestamps numériques, pas de valeurs formatées selon une locale.
Un client TypeScript généré
La spécification servie à /openapi.json est tout ce dont un client a besoin.
Générez-en les types avec openapi-typescript et appelez l'API avec
openapi-fetch :
npm install openapi-fetch
npx openapi-typescript https://cms.spunto.net/openapi.json -o spunto-api.d.tsimport createClient from "openapi-fetch"
import type { paths } from "./spunto-api"
const api = createClient<paths>({
baseUrl: "https://cms.spunto.net/api",
headers: { Authorization: `Bearer ${process.env.SPUNTO_TOKEN}` },
})
const { data, error } = await api.GET("/workspaces")C'est exactement ainsi que le front de ce produit parle à l'API : son client est généré par les deux mêmes outils, depuis la même spécification commitée, si bien qu'une route qui change de forme devient une erreur de typage plutôt qu'une surprise à l'exécution. Ce paquet interne n'est pas publié sur npm ; générer le vôtre tient en une commande et vous fixe à la version de l'API contre laquelle vous l'avez généré.
Il existe aussi un client Swift, généré depuis la même spécification au build de l'app iOS.
Les champs de réponse sont ajoutés en optionnel
Un champ ajouté à un schéma de réponse est optionnel, toujours. Le générateur Swift décode strictement : un champ requis de plus ne dégrade pas un client qui n'a pas été mis à jour — il fait échouer la réponse entière. Comme les clients se déploient séparément de l'API, « l'app est en avance sur le serveur » est un état normal, pas un incident. Écrivez vos consommateurs en conséquence.
Limites de débit
Il n'y en a aucune aujourd'hui. C'est un constat sur l'implémentation actuelle, pas une promesse — ne construisez rien qui dépende de leur absence.
Corps des cartes
Le corps d'une carte est un document en blocs — validé à l'écriture, incapable de transporter du HTML, et toujours lisible aussi en texte brut.
Lire le contenu
Lire des cartes par l'API REST avec une définition de vue — filtres, tris, curseurs keyset, pages groupées, comptages et recherche.