Catalogue
list_marketplace_skills liste toutes les compétences publiées. Il exige hubs:read et aucun forfait payant.
Parcourez les compétences de la place de marché, puis créez un groupe de runtime et un hub, installez une compétence et publiez-les depuis votre code.
Consultez cette page quand votre code doit créer le hub au lieu de passer par le tableau de bord.
Le provisionnement comporte cinq étapes : découvrir les compétences dans le catalogue de la place de marché, créer un groupe de runtime, créer un hub qui y est rattaché, installer une compétence dans le groupe, puis publier les deux sur un canal. Python, Node.js, Go, Rust, Kotlin, Swift et .NET exposent ces appels, et le serveur MCP les expose sous forme d’outils. Le C embarqué fournit des fonctions d’aide pour le protocole du runtime; provisionnez son hub et son identité de client séparément.
| Étape | Appel | Ce qu’il faut |
|---|---|---|
| Découvrir les compétences | list_marketplace_skills | hubs:read. N’importe quel forfait. |
| Créer un groupe de runtime | create_runtime_group | hubs:write et un forfait payant. |
| Créer un hub | create_hub | hubs:write et un forfait payant. |
| Installer une compétence | install_runtime_group_skill | hubs:write, un forfait payant et un accès à la place de marché pour les compétences payantes. |
| Publier | release_runtime_group et release_hub | hubs:write et un forfait payant. Les images épinglées doivent être des images que la plateforme publie, sauf si vous êtes administrateur de la plateforme; consultez la ligne platform_image_required dans Guide des erreurs. |
Les noms de méthodes suivent les conventions de chaque langage. Le tableau utilise l’orthographe Python; consultez Noms des appels par langage pour les autres.
| Portée | Ce qu’elle couvre |
|---|---|
hubs:read | Le catalogue de la place de marché et la liste des groupes de runtime. |
hubs:inspect | L’inventaire d’un groupe de runtime et la vue résolue de la place de marché pour ce groupe. |
hubs:write | La création, la mise à jour, la publication et la suppression des hubs et des groupes de runtime, ainsi que l’installation de compétences. |
Ces portées s’impliquent les unes les autres. hubs:write accorde hubs:read, qui accorde hubs:inspect. Un jeton créé avec hubs:read couvre tous les appels de découverte de cette page.
Ce flux découvre des compétences, crée un groupe de runtime et un hub, installe une compétence et publie les deux.
Les exemples créent un hub Standard. La capacité Autoscaling est un réglage distinct du hub, contrôlé par le forfait de l’espace de travail. Modifier les réglages de réplicas d’un groupe de runtime ne l’active pas.
import os
from thalovant import ThalovantControlPlane
api = ThalovantControlPlane(access_token=os.environ["THALOVANT_API_TOKEN"])
# 1. Discover what is installable before provisioning anything.for skill in api.list_marketplace_skills()["data"]: print(skill["skill_id"], skill["title"], skill["access_tier"])
# 2. Create a runtime group to run the skills.group = api.create_runtime_group({"name": "kiosks", "description": "Lobby kiosks"})
# 3. Create a hub attached to it.hub = api.create_hub( { "name": "joke-garden", "runtime_group_id": group["id"], "spec": {"protocols": {"wss": {"enabled": True}}}, })
# 4. Install a skill from the marketplace catalog.api.install_runtime_group_skill(group["id"], "skill-weather")
# 5. Roll the runtime group and the hub onto a release channel.api.release_runtime_group(group["id"], channel="stable")api.release_hub(hub["id"], channel="stable")import { ThalovantControlPlane } from "@thalovant/sdk";
const api = new ThalovantControlPlane(undefined, { accessToken: process.env.THALOVANT_API_TOKEN,});
// 1. Discover what is installable before provisioning anything.const catalog = await api.listMarketplaceSkills();for (const skill of catalog.data as Array<Record<string, unknown>>) { console.log(skill.skill_id, skill.title, skill.access_tier);}
// 2. Create a runtime group to run the skills.const group = await api.createRuntimeGroup({ name: "kiosks", description: "Lobby kiosks" });
// 3. Create a hub attached to it.const hub = await api.createHub({ name: "joke-garden", runtimeGroupId: group.id as string, spec: { protocols: { wss: { enabled: true } } },});
// 4. Install a skill from the marketplace catalog.await api.installRuntimeGroupSkill(group.id as string, "skill-weather");
// 5. Roll the runtime group and the hub onto a release channel.await api.releaseRuntimeGroup(group.id as string, { channel: "stable" });await api.releaseHub(hub.id as string, { channel: "stable" });control := thalovant.NewDefaultControlPlane(os.Getenv("THALOVANT_API_TOKEN"))
// 1. Discover what is installable before provisioning anything.catalog, err := control.ListMarketplaceSkills(ctx, thalovant.MarketplaceSkillListOptions{})if err != nil { log.Fatal(err)}
// 2. Create a runtime group to run the skills.group, err := control.CreateRuntimeGroup(ctx, map[string]any{ "name": "kiosks", "description": "Lobby kiosks",})if err != nil { log.Fatal(err)}groupID := group["id"].(string)
// 3. Create a hub attached to it.hub, err := control.CreateHub(ctx, map[string]any{ "name": "joke-garden", "runtime_group_id": groupID, "spec": map[string]any{"protocols": map[string]any{"wss": map[string]any{"enabled": true}}},}, thalovant.HubCreateOptions{})if err != nil { log.Fatal(err)}hubID := hub["id"].(string)
// 4. Install a skill from the marketplace catalog.if _, err := control.InstallRuntimeGroupSkill(ctx, groupID, "skill-weather", thalovant.RuntimeGroupSkillInstallOptions{}); err != nil { log.Fatal(err)}
// 5. Roll the runtime group and the hub onto a release channel.control.ReleaseRuntimeGroup(ctx, groupID, thalovant.ReleaseOptions{Channel: "stable"})control.ReleaseHub(ctx, hubID, thalovant.ReleaseOptions{Channel: "stable"})use serde_json::json;use thalovant::{ControlPlane, MarketplaceSkillsOptions, ReleaseOptions, SkillInstallOptions};
let control = ControlPlane::with_access_token(std::env::var("THALOVANT_API_TOKEN")?);
// 1. Discover what is installable before provisioning anything.let catalog = control .list_marketplace_skills(MarketplaceSkillsOptions::default()) .await?;
// 2. Create a runtime group to run the skills.let group = control .create_runtime_group(json!({"name": "kiosks", "description": "Lobby kiosks"})) .await?;let group_id = group["id"].as_str().unwrap_or_default().to_string();
// 3. Create a hub attached to it.let hub = control .create_hub( json!({ "name": "joke-garden", "runtime_group_id": group_id, "spec": {"protocols": {"wss": {"enabled": true}}}, }), None, ) .await?;let hub_id = hub["id"].as_str().unwrap_or_default().to_string();
// 4. Install a skill from the marketplace catalog.control .install_runtime_group_skill(&group_id, "skill-weather", SkillInstallOptions::default()) .await?;
// 5. Roll the runtime group and the hub onto a release channel.let channel = || ReleaseOptions { channel: Some("stable".into()), ..Default::default()};control.release_runtime_group(&group_id, channel()).await?;control.release_hub(&hub_id, channel()).await?;Kotlin, Swift et .NET exposent les mêmes cinq appels avec des noms natifs. Leurs pages de SDK montrent la forme propre à chaque langage.
Les méthodes de compétence adressées à un hub gèrent le groupe de runtime auquel ce hub est rattaché, ce qui touche tous les hubs qui partagent ce groupe. list_hub_skills renvoie les lignes de compétences du hub sous data, chacune avec un installed_version, un update_available et un state; install_hub_skill, update_hub_skill et remove_hub_skill sélectionnent le groupe de runtime à partir de l’identifiant du hub, renvoient une opération dont vous pouvez attendre la fin et s’appliquent à tous les hubs qui partagent ce groupe. La liste exige hubs:inspect; les écritures exigent hubs:write et un forfait payant, et un hub qui n’est pas encore dans un groupe de runtime répond HTTP 404 avec le code hub_without_runtime_group. Les sept SDK gérés fournissent maintenant ces méthodes et l’historique des compétences dans leurs versions actuelles; consultez Ajouter une compétence à un hub pour les routes, le parcours dans l’application et les erreurs.
Générez et conservez une clé d’idempotence explicite avant la première tentative de création d’un hub. Réutilisez cette clé et le même contenu quand vous réessayez cette même création logique après un délai d’attente réseau dépassé. Sans clé, chaque appel au SDK en génère une nouvelle, de sorte qu’un appel distinct peut créer un deuxième hub. Les SDK ne réessaient pas automatiquement une création. Réutiliser une clé avec un corps différent provoque une erreur; consultez le tableau ci-dessous.
La création d’un groupe de runtime et l’installation d’une compétence ne prennent pas de clé d’idempotence.
Une compétence du catalogue n’est pas toujours installable avec votre forfait. La vue de la place de marché du groupe résout le catalogue pour un groupe de runtime précis et indique si chaque entrée peut être installée. Vous pouvez ainsi décider d’avance au lieu de lire une erreur après coup.
Catalogue
list_marketplace_skills liste toutes les compétences publiées. Il exige hubs:read et aucun forfait payant.
Vue de la place de marché du groupe
list_runtime_group_marketplace résout le catalogue pour un groupe de runtime et indique si chaque compétence est installable et s’il exige un achat. Il exige hubs:inspect.
Inventaire du groupe
list_runtime_group_inventory liste les compétences réellement observées en cours d’exécution dans le groupe. Il exige hubs:inspect.
La mise à jour et la suppression d’un hub utilisent le verrouillage optimiste. Les deux exigent l’etag actuel du hub, que le SDK envoie dans un en-tête If-Match.
L’etag se trouve seulement dans le corps de la ressource du hub. L’API n’envoie aucun en-tête de réponse ETag. Lisez donc d’abord le hub et prenez le champ etag dans cette réponse.
hub = api.get_hub(hub_id)hub = api.update_hub(hub_id, {"active": False}, etag=hub["etag"])api.delete_hub(hub_id, etag=hub["etag"])let hub = await api.getHub(hubId);hub = await api.updateHub(hubId, { active: false }, { etag: hub.etag as string });await api.deleteHub(hubId, { etag: hub.etag as string });hub, err := control.GetHub(ctx, hubID)if err != nil { log.Fatal(err)}
hub, err = control.UpdateHub(ctx, hubID, map[string]any{"active": false}, hub["etag"].(string))if err != nil { log.Fatal(err)}
if err := control.DeleteHub(ctx, hubID, hub["etag"].(string)); err != nil { log.Fatal(err)}let hub = control.get_hub(&hub_id).await?;let etag = hub["etag"].as_str().unwrap_or_default().to_string();
let hub = control .update_hub(&hub_id, json!({"active": false}), &etag) .await?;
let etag = hub["etag"].as_str().unwrap_or_default().to_string();control.delete_hub(&hub_id, &etag).await?;Passez un etag actuel non vide. Les SDK qui offrent une validation locale rejettent les valeurs vides avant l’envoi; un etag périmé atteint l’API et échoue avec HTTP 412 et le détail ETag mismatch. Relisez le hub et réconciliez vos changements avant de réessayer avec son nouvel etag.
Les écritures sur les groupes de runtime n’utilisent pas d’etag. Les ressources de groupe de runtime ne portent aucun etag à renvoyer, et aucune route de groupe de runtime ne lit If-Match.
Les anciennes mises à jour de configuration de groupe de runtime par PATCH remplacent la configuration,
sauf la section de contrôle protégée. Les clés de runtime omises sont supprimées; personas
ne change que si vous le fournissez. Coordonnez les rédacteurs qui utilisent cette route inconditionnelle.
Pour une mise à jour protégée, lisez GET /v1/runtime-groups/{id}/config, fusionnez
vos changements dans son config, puis envoyez PUT au même chemin avec config et
expected_revision réglé sur la revision renvoyée. HTTP 412 signifie que rien n’a été
écrit : relisez et appliquez de nouveau vos changements d’origine. PUT exige une révision;
l’ancien PATCH reste pris en charge. Les anciennes versions de l’API rejettent PUT sans rien écrire.
Python 0.7.5, Node/Swift/.NET/Kotlin 0.7.1, Go 0.9.1 et Rust 0.9.0,
ainsi que MCP 0.5.1, utilisent ce flux protégé par défaut, avec au plus trois tentatives
d’écriture. Seul HTTP 412 déclenche une nouvelle lecture et une nouvelle fusion du delta d’origine.
Les objets fusionnent de façon récursive; les tableaux et les scalaires sont remplacés. Des personas omis restent
inchangés; des personas fournis remplacent les précédents. La fusion exige les portées hubs:read et
hubs:write et un forfait payant. Un serveur plus ancien sans révision valide échoue avant toute écriture;
il n’y a aucun repli inconditionnel.
Pour un remplacement complet voulu, utilisez merge=False en Python, merge: false en Node/MCP,
ou l’équivalent replaceRuntimeGroupConfig des autres SDK.
Le remplacement exige la portée hubs:write, un forfait payant et une coordination avec les autres rédacteurs.
Si vous mettez à niveau du code qui comptait sur le remplacement, sélectionnez-le explicitement.
Le name, le namespace et le domain d’un hub sont fixés à la création. Envoyer une nouvelle valeur pour l’un d’eux échoue avec HTTP 400 et un message comme Name cannot be changed after hub creation. Choisissez-les avec soin : donner un nouveau nom à un hub oblige à créer un nouveau hub et à y reconnecter ses clients.
Renvoyer la valeur actuelle est accepté. Le champ est retiré de la mise à jour au lieu d’être rejeté, de sorte qu’un cycle lecture-modification-écriture qui transporte ces champs ne se brise pas.
Les champs modifiables comprennent active, visibility, slug, spec, capacity_profile et le groupe de runtime auquel le hub se rattache. Utilisez le flux avec etag décrit plus haut et n’envoyez que les champs de mise à jour acceptés. Le forfait détermine le plafond de workers quand vous choisissez la capacité Autoscaling.
Utilisez ce tableau quand vous connaissez déjà la tâche et qu’il vous faut le nom propre au langage.
| Tâche | Python | Node.js | Go | Rust |
|---|---|---|---|---|
| Lister les compétences du catalogue | list_marketplace_skills(...) | listMarketplaceSkills(...) | ListMarketplaceSkills(ctx, opts) | list_marketplace_skills(opts) |
| Créer un hub | create_hub(payload, ...) | createHub(payload, options) | CreateHub(ctx, payload, opts) | create_hub(payload, idempotency_key) |
| Obtenir un hub | get_hub(hub_id) | getHub(hubId) | GetHub(ctx, hubID) | get_hub(hub_id) |
| Mettre à jour un hub | update_hub(id, payload, etag=) | updateHub(id, payload, { etag }) | UpdateHub(ctx, id, payload, etag) | update_hub(id, payload, etag) |
| Supprimer un hub | delete_hub(id, etag=) | deleteHub(id, { etag }) | DeleteHub(ctx, id, etag) | delete_hub(id, etag) |
| Créer un groupe de runtime | create_runtime_group(payload) | createRuntimeGroup(payload) | CreateRuntimeGroup(ctx, payload) | create_runtime_group(payload) |
| Lister les groupes de runtime | list_runtime_groups(...) | listRuntimeGroups(options) | ListRuntimeGroups(ctx, ownerID) | list_runtime_groups(owner_id) |
| Inventaire du groupe | list_runtime_group_inventory(...) | listRuntimeGroupInventory(...) | ListRuntimeGroupInventory(ctx, id, opts) | list_runtime_group_inventory(id, refresh) |
| Vue de la place de marché du groupe | list_runtime_group_marketplace(...) | listRuntimeGroupMarketplace(...) | ListRuntimeGroupMarketplace(ctx, id, opts) | list_runtime_group_marketplace(id, refresh) |
| Installer une compétence | install_runtime_group_skill(...) | installRuntimeGroupSkill(...) | InstallRuntimeGroupSkill(ctx, id, skill, opts) | install_runtime_group_skill(id, skill, opts) |
| Publier un groupe de runtime | release_runtime_group(...) | releaseRuntimeGroup(id, options) | ReleaseRuntimeGroup(ctx, id, opts) | release_runtime_group(id, opts) |
| Publier un hub | release_hub(...) | releaseHub(id, options) | ReleaseHub(ctx, id, opts) | release_hub(id, opts) |
| Lister les compétences d’un hub | list_hub_skills(hub_id) | listHubSkills(hubId) | à venir | à venir |
| Installer une compétence de hub | install_hub_skill(hub_id, skill, version=, wait=) | installHubSkill(hubId, skill, { version, wait }) | à venir | à venir |
| Mettre à jour une compétence de hub | update_hub_skill(hub_id, skill, version=, wait=) | updateHubSkill(hubId, skill, { version, wait }) | à venir | à venir |
| Retirer une compétence de hub | remove_hub_skill(hub_id, skill, wait=) | removeHubSkill(hubId, skill, { wait }) | à venir | à venir |
Kotlin et Swift utilisent l’orthographe de Node.js avec des types d’options natifs, comme createHub et updateHub(id, payload, etag). C# ajoute le suffixe asynchrone, comme CreateHubAsync et UpdateHubAsync(id, options, etag). Les pages des SDK Kotlin, Swift et .NET montrent les signatures exactes.
| Statut | Détail | Ce que cela signifie et quoi faire |
|---|---|---|
| 402 | API access requires a paid plan. | L’espace de travail est sur le forfait Free. L’API vérifie ce point avant les portées du jeton. Passez l’espace de travail à un forfait payant, puis réessayez. Les appels de découverte continuent de fonctionner avec le forfait Free. |
| 403 | Insufficient scopes | Le forfait est payant, mais il manque au jeton la portée qu’exige la route. Créez un jeton avec hubs:write pour le provisionnement, ou hubs:read pour le catalogue. |
| 402 | This skill requires paid marketplace access for the tenant plan. | Le forfait est payant, mais il ne couvre pas cette entrée payante du catalogue. Consultez d’abord la vue de la place de marché du groupe, qui indique installable, purchase_required et access_message pour chaque compétence. |
| 412 | ETag mismatch | L’etag If-Match d’une mise à jour ou d’une suppression de hub était absent ou périmé. Relisez le hub, prenez l’etag dans le corps de la réponse et réessayez. Rien n’a été modifié. |
| 409 | Idempotency key re-used with different payload | Une création de hub a réutilisé une clé d’idempotence avec un corps différent. Utilisez une nouvelle clé pour un hub réellement nouveau, ou renvoyez exactement le corps d’origine pour rejouer le premier résultat. |
| 400 | Name cannot be changed after hub creation | La mise à jour a tenté de modifier name, namespace ou domain. Créez plutôt un nouveau hub. |
| 403 | Only an administrator can run an image the platform does not release: … | Le code est platform_image_required. Une publication, ou une mise à jour de hub qui modifiait spec.image ou spec.previewBridge.image, a nommé une image que la plateforme ne livre pas. Omettez images pour prendre la publication recommandée, ou épinglez, pour chaque clé d’image, une image épinglée au catalogue du canal stable ou alpha, l’image actuelle ou recommandée de la ressource, l’image de sa politique de publication ou l’image par défaut de la plateforme. Le core d’un groupe de runtime accepte aussi n’importe quelle étiquette ou empreinte de ghcr.io/thalovant/ovos-core, et le listener d’un hub n’importe quelle étiquette ou empreinte de ghcr.io/thalovant/hivemind-listener. bus et preview_bridge n’acceptent que les images listées pour eux. Les champs allowed_images et allowed_repositories de l’erreur indiquent ce que chaque clé refusée peut contenir. Seul un administrateur de la plateforme peut exécuter autre chose. |
La vérification du forfait a lieu avant celle des portées, donc les deux ne se chevauchent jamais. Un appel de provisionnement sur le forfait Free répond 402 API access requires a paid plan., quelles que soient les portées du jeton. Ce message indique la vraie solution : aucune portée qu’un jeton Free peut détenir ne ferait fonctionner l’appel, parce qu’un jeton du forfait Free ne peut jamais porter hubs:write.
Seul un espace de travail payant atteint la vérification des portées. Là, un jeton sans hubs:write répond 403 Insufficient scopes.
Lisez un 402 sur un appel de provisionnement comme « cet espace de travail ne peut pas faire cela », et un 403 comme « ce jeton ne peut pas faire cela ». Le seul 403 qui concerne autre chose est platform_image_required : le jeton est correct, c’est l’image qui ne l’est pas. Les deux 402 ont la même forme; distinguez-les par leur texte de détail.
Le message d’erreur tient sur une ligne et peut être tronqué. Lisez donc plutôt un refus à partir des champs de l’erreur. La phrase de platform_image_required nomme toutes les images que chaque clé refusée peut contenir, et elle est plus longue que la ligne de message de n’importe quel SDK. problem contient le corps complet de l’erreur quand c’est un objet JSON, de sorte qu’un champ que l’API ajoutera plus tard s’y trouvera sans nouvelle version du SDK.
| SDK | Statut | Code | Phrase complète | Corps complet |
|---|---|---|---|---|
| Python 0.8.7 | status_code | code | detail | problem (dict) |
| Node.js 0.8.7 | statusCode | code | detail | problem (objet) |
| Go v0.10.6 | StatusCode | Code | ProblemDetail | Problem (map[string]any) |
| Rust v0.12.0 | status_code() | api_code() | api_detail() | api_problem() (ApiProblem) |
| Kotlin 0.7.17 | statusCode | errorCode | detail | problem (JsonObject) |
| Swift 0.9.0 | statusCode | errorCode | detail | problem (JSONObject) |
| .NET 0.8.6 | StatusCode | ErrorCode | Detail | Problem (JsonObject) |
Pour platform_image_required, problem contient refused_images, allowed_images (par clé d’image) et allowed_repositories (les clés qui acceptent n’importe quelle étiquette ou empreinte). Pour plan_limit, il contient resource, limit, used et plan.
from thalovant import ThalovantAPIError
try: api.release_runtime_group(group["id"], images={"core": "docker.io/me/ovos-core:dev"})except ThalovantAPIError as error: if error.code != "platform_image_required": raise print(error.detail) print(error.problem["allowed_images"])Une valeur de votre requête qu’une erreur de validation renvoie reste dans problem et n’atteint jamais le message. Le serveur MCP place le code, la phrase complète et les autres champs dans le texte d’erreur de l’outil; consultez Serveur MCP.
Le serveur MCP expose le même flux sous forme d’outils. Un agent peut ainsi découvrir des compétences et provisionner un hub sans écrire de code SDK. Les outils de suppression ne sont pas offerts par défaut; consultez Outils destructifs.