API HTTP Gateway
La passerelle expose une surface REST aux côtés du CLI local. Tout ce qui peut être défini avec zeroclaw config get/set/list/init/migrate est également accessible via HTTP, de sorte que le tableau de bord, les outils tiers et le CLI pilotent tous le même cœur de mutation Config sous-jacent.
Cette page constitue une vue d’ensemble générale. Les définitions au niveau des champs, les structures des requêtes et des réponses, ainsi que les formulaires « Try it out » pour le sous-ensemble OpenAPI actuellement documenté se trouvent à /api/docs sur une passerelle en cours d’exécution. Ces schémas proviennent des types d’exécution, mais l’inventaire des routes est assemblé séparément et ne couvre pas encore toutes les routes enregistrées par la passerelle. Le routeur dans crates/zeroclaw-gateway/src/lib.rs reste la référence pour l’ensemble de la surface active.
Suivi dans le ticket #6175.
Authentification
Les lectures et mutations de valeurs de configuration décrites sur cette page sont soumises à l’authentification par appairage et par jeton bearer existante. La découverte de forme via /api/docs, /api/openapi.json, et OPTIONS de configuration est publique. Un code d’appairage de première exécution est affiché au démarrage du daemon ; les appels authentifiés suivants envoient le jeton bearer dérivé dans l’en-tête Authorization. L’explorateur Scalar disponible à /api/docs expose un panneau « Authentication » où vous collez le jeton avant d’émettre des appels authentifiés.
Limité au réseau local par défaut. L’accès via le réseau nécessite une terminaison TLS au niveau de la passerelle ou en amont de celle-ci ; les points de terminaison par propriété et PATCH ne peuvent pas être exposés sans authentification, quelle que soit la configuration TLS.
Ingress par webhook des plugins de canal
Les builds avec prise en charge des plugins WASM exposent GET et POST /plugin/{path} lorsqu’un plugin de canal en cours d’exécution et explicitement configuré revendique ce chemin. Il s’agit d’une frontière de transport brute : la passerelle conserve la méthode HTTP, la chaîne de requête brute, les octets du corps et les en-têtes UTF-8 en minuscules dans une requête typée transmise à l’export parse-webhook du composant. Le composant vérifie sa requête de la plateforme à l’aide de son secret à portée limitée, puis renvoie soit des messages normalisés, soit une réponse de défi explicite. La requête n’exécute jamais un tour d’agent en ligne, et les réponses n’entrent jamais dans la file d’attente de l’agent.
Un chemin revendiqué doit comporter entre 1 et 64 lettres ASCII, chiffres, tirets ou tirets bas. Les revendications en double ou invalides sont rejetées avant que le démon ne publie de manière atomique sa table de routage d’exécution.
| Statut | Organisme public | Signification |
|---|---|---|
200 | vide ou texte de défi | La requête vérifiée a transmis les messages autorisés, ignoré les doublons et les expéditeurs refusés, ou renvoyé un défi de réponse uniquement, d’au plus 4096 octets UTF-8. |
| 400 | invalid webhook | Le client invité a authentifié la requête, mais a rejeté la structure de sa charge utile. |
| 401 | webhook non autorisé | Échec de la vérification de l’authenticité de la plateforme invité. |
| 404 | webhook introuvable | Aucun canal de plugin actif ne gère ce chemin. |
| 405 | vide, en-tête Allow: GET, POST | HEAD ou une autre méthode non prise en charge ; le composant n’est pas appelé. |
| 429 | JSON de limitation de débit ou webhook queue full | La limite canonique de webhooks par client ou la file d’attente bornée de la route a refusé l’admission. |
| 502 | réponse de webhook invalide | A component response exceeded the 4096-byte limit. |
| 503 | webhook unavailable | Le composant, le service hôte ou le récepteur du canal en aval est indisponible. |
| 504 | webhook processing timed out → le traitement du webhook a expiré | La durée de vie de la requête de dix secondes a annulé l’analyse ou l’envoi. |
La passerelle ne renvoie jamais de détails sur l’invité, Wasmtime, les secrets, la configuration ou les erreurs en aval sur cette surface non authentifiée. Ces détails sont bornés et journalisés avec l’attribution au plugin. La limite standard de 64 KiB pour le corps de la passerelle s’applique. Après l’authentification de l’invité, l’hôte applique le groupe de pairs actif pour plugin.<alias> avant la réservation d’idempotence et la livraison. Les identifiants de message stables utilisent des réservations à espaces de noms par route, sûres vis-à-vis de la propriété : un doublon en cours attend la validation ou l’annulation au lieu d’être acquitté prématurément.
Explorer la surface
Deux endpoints répondent à la question « que puis-je faire ici ? » :
OPTIONS /api/configretourne le schéma JSON pour le type de configuration globale. Statique par build ; les clients devraient mettre en cache en fonction de l’en-têteETag. Son en-têteAllowactuel liste toujours l’ancienPUT, que le routeur n’enregistre pas.OPTIONS /api/config/prop?path=<dotted>renvoie le fragment de schéma pour un chemin spécifique avecAllow: GET, PUT, DELETE, OPTIONS. Renvoie 404 si le chemin n’existe pas dans le schéma.
OPTIONS renvoie les capacités. GET /api/config/prop et GET /api/config/list renvoient les valeurs actuelles de l’utilisateur. Les formulaires du tableau de bord émettent OPTIONS une fois au chargement pour connaître les types et les contraintes, puis GET pour remplir les champs, puis PUT/PATCH pour écrire. Une route de compatibilité GET /api/config renvoie également un instantané complet de la configuration avec les secrets masqués, afin que les anciennes pages du tableau de bord intégré ne rencontrent pas d’échec face à des passerelles plus récentes. Les nouveaux clients devraient privilégier la surface par propriété, car elle transporte les métadonnées des champs et une gestion explicite des secrets.
Les requêtes de pré-vérification CORS (celles portant l’en-tête Access-Control-Request-Method) reçoivent la réponse de pré-vérification standard et sont court-circuitées avant que le corps du schéma ne soit retourné.
GET /api/plugins renvoie le catalogue de paquets en lecture seule, authentifié, utilisé par le tableau de bord. Il matérialise une ligne par nom de paquet, à partir des manifestes installés admis par l’hôte et du registre déjà mis en cache, en appliquant la même règle de sélection du registre non épinglée que plugin install. Les enregistrements installés et ceux du registre restent séparés, afin que les clients puissent afficher les deux versions sans deviner qu’une mise à niveau existe. Le point de terminaison n’effectue aucune récupération du registre et aucune mutation.
La route existe dans toutes les versions de la passerelle. Lorsque la prise en charge des plugins WASM est absente, elle renvoie wasm_plugins_available: false et une source de paquets vide au lieu d’une 404. [plugins].enabled indique l’intention de configuration, et non l’état d’exécution. Les codes stables de issues distinguent les échecs de source d’un catalogue valide mais vide, tandis que les diagnostics détaillés restent dans les journaux de la passerelle. Les URL de téléchargement du registre et l’URL de registre mise en cache ne sont jamais renvoyées ; un enregistrement disponible expose uniquement l’identité d’installation inerte name@version.
La découverte du catalogue s’exécute sur un worker bloquant car l’admission de l’hôte lit et vérifie les charges utiles WASM installées. Un seul scan s’exécute à la fois ; une requête concurrente reçoit 503 Service Unavailable et peut réessayer. L’endpoint ne met pas en cache l’état des paquets admis entre les requêtes.
CRUD par propriété
| Méthode | Chemin | Objectif |
|---|---|---|
GET | /api/config | Instantané complet de la configuration pour compatibilité avec les secrets masqués ; les nouveaux clients devraient privilégier la surface par propriété. |
PATCH | /api/config | Applique un document JSON Patch (RFC 6902) de manière atomique. |
OPTIONS | /api/config | Schéma JSON de configuration complète (capacités, pas valeurs). |
GET | /api/config/prop?path=... | Lire un champ. Les secrets retournent uniquement {path, populated}. |
PUT | /api/config/prop | Écrit un champ. Corps : {path, value, comment?}. Les secrets répondent uniquement avec {path, populated: true}. |
DELETE | /api/config/prop?path=... | Réinitialise un champ à sa valeur par défaut. Les secrets répondent avec {path, populated: false}. |
OPTIONS | /api/config/prop?path=... | Fragment de schéma par champ. |
GET | /api/config/list?prefix=... | Énumère chaque chemin accessible avec son type et sa catégorie. Les entrées secrètes contiennent {path, populated, is_secret: true} et aucune valeur. |
POST | /api/config/init?section=... | Instancier les sections imbriquées None avec les valeurs par défaut. Les alias de map dynamique ne sont pas créés ici ; utilisez POST /api/config/map-key. |
POST | /api/config/migrate | Appliquer la migration de schéma sur disque en place. Reflète zeroclaw config migrate. |
Écritures atomiques par lots : JSON Patch
PATCH /api/config accepte un document JSON Patch (RFC 6902). Les opérations de configuration prises en charge sont add, replace, remove et test. ZeroClaw accepte également une extension comment pour les annotations de configuration. Les opérations de configuration s’exécutent sur une copie en mémoire ; une fois toutes les opérations appliquées, Config::validate() s’exécute une seule fois sur le résultat. Si la validation réussit, le nouvel état est persisté et mis en service. Si une opération quelconque ou la validation finale échoue, les états sur disque et en mémoire restent inchangés. Les annotations de commentaires sont appliquées après la sauvegarde de manière non bloquante et selon le principe du meilleur effort.
move et copy renvoient 400 op_not_supported car la réécriture sécurisée du graphe de références ne fait pas partie de cette surface. test sur un chemin #[secret] est rejeté avec secret_test_forbidden : un résultat différentiel serait le seul signal qu’un client pourrait lire, et cela divulguerait la valeur.
Syntaxe de chemin : JSON Pointer (/agents/researcher/model_provider) ou la forme avec points (agents.researcher.model_provider). Les deux sont acceptées ; le serveur normalise.
L’équivalent CLI est zeroclaw config patch <file-or-stdin>, qui applique le même ensemble d’opérations sur la Config locale et renvoie la même structure de réponse structurée (--json pour les scripts).
Secrets : écriture seule via HTTP
Les lectures par propriété n’exposent jamais les champs secrets (ceux marqués #[secret] ou #[derived_from_secret] dans le schéma). Leurs réponses ne comportent que {populated: bool}, sans valeur, longueur, substitut masqué ni hachage. Le point d’accès de compatibilité GET /api/config sérialise à la place l’ensemble de la configuration après application de MaskSecrets, de sorte que les champs secrets ne peuvent y apparaître que sous forme d’espaces réservés masqués. Aucune de ces surfaces de lecture de configuration ne renvoie la valeur secrète sous-jacente.
PUT et PATCH écrivent la nouvelle valeur du secret et répondent avec {populated: true} ; DELETE l’efface et répond avec {populated: false}. Il n’existe aucun chemin HTTP permettant de récupérer un secret, par quelque moyen que ce soit.
Codes d’erreur stables
Les erreurs renvoient du JSON avec un champ code stable et un message lisible par l’utilisateur. Les frontends et les scripts effectuent une correspondance avec le code ; l’interface utilisateur effectue une correspondance avec le chemin.
| Code | Statut | Signification |
|---|---|---|
path_not_found | 404 | La propriété demandée n’existe pas dans le schéma. |
validation_failed | 400 | Le validateur de configuration globale a rejeté l’état proposé. |
dangling_reference | 400 | Une référence d’alias configurée (par ex. agents.<x>.model_provider) désigne une cible manquante (par ex. providers.models.<type>.<alias>). |
value_type_mismatch | 400 | La valeur JSON soumise ne peut pas être convertie vers le type cible. |
op_not_supported | 400 | L’opération JSON Patch est move / copy / inconnue. |
secret_test_forbidden | 400 | L’opération test du JSON Patch ciblait un chemin secret. |
config_changed_externally | 409 | La configuration sur disque a divergé de la copie en mémoire. (Voir détection de divergence.) |
reload_failed | 500 | La sauvegarde a réussi mais le rechargement du démon n’a pas pu prendre en compte le nouvel état ; restauration sur le disque effectuée. |
internal_error | 500 | Échec côté serveur non classifié. |
Exploration en direct
Une fois qu’une passerelle est en cours d’exécution, accédez à http://<gateway-host>:<port>/api/docs pour l’explorateur d’API Scalar. La spécification brute est disponible à l’adresse /api/openapi.json pour les autres visionneuses compatibles.
Le panneau d’authentification de l’explorateur se lie au schéma bearerAuth déclaré dans la spécification ; collez-y votre jeton bearer dérivé de l’appariement avant d’effectuer des appels en direct. Le raccourci CLI pour l’URL est zeroclaw config docs.
Si le bundle Scalar ne peut pas être chargé depuis le CDN (installation hors ligne / isolée), la page se dégrade correctement et vous redirige vers la spécification brute à l’adresse /api/openapi.json afin que vous puissiez utiliser n’importe quel visualiseur compatible (Insomnia, Postman, Swagger UI, etc.).
Contrat de flux d’événements
GET /api/events est un flux brut Server-Sent Events d’événements d’exécution observables. Ce n’est pas une chronologie de cycle de vie dédupliquée avec une ligne par tour.
Les gestionnaires de passerelle, le traitement des webhooks, les tâches cron/heartbeat et les observateurs de boucle d’agent peuvent tous publier des événements de type cycle de vie dans le même chemin de diffusion. Les clients doivent considérer le flux comme un journal d’observation en ajout seul. Si un tableau de bord souhaite une chronologie de tours compacte, il doit regrouper ou dédupliquer selon les identifiants présents dans la charge utile de l’événement plutôt que de supposer que chaque trame agent_start, llm_request ou agent_end n’apparaît qu’une seule fois.
GET /api/events/history rejoue les événements récents conservés dans le même tampon, du plus ancien au plus récent. Il s’agit d’une fenêtre de reconnexion pour les abonnés, et non d’un magasin de cycle de vie canonique distinct.