Finding and provisioning hubs
List hubs, read one, create one, update it, and follow the operation that results.
Part of SDK Functions. Every call below is shown in each language that has it.
Call A Hub By Its Name
Section titled “Call A Hub By Its Name”A hub has a slug, a name, and sometimes a catalog title. Showing the slug to a person is showing them plumbing, so every SDK ships one function that picks the best of the three and falls back to a readable form of the slug.
from thalovant.hubs import hub_display_name
hub_display_name({"slug": "kitchen-hub"}) # "Kitchen Hub"hub_display_name({"slug": "kitchen-hub", "name": "Kitchen"}) # "Kitchen"hub_display_name({}) # "A Thalovant hub"hubDisplayName({ slug: "kitchen-hub" }); // "Kitchen Hub"thalovant.HubDisplayName(map[string]any{"slug": "kitchen-hub"}) // "Kitchen Hub"thalovant::hubs::hub_display_name(&hub) // "Kitchen Hub"hubDisplayName(hub) // "Kitchen Hub"hubDisplayName(hub) // "Kitchen Hub"Hubs.DisplayName(hub); // "Kitchen Hub"The order is catalog title, then name, then slug — and a name that is exactly the slug is treated as the slug, because it carries nothing the slug does not. With none of the three, the answer is A Thalovant hub rather than an empty label.
List Public Hubs
Section titled “List Public Hubs”Use public hub discovery before you ask a user to choose a hub.
page = api.list_public_hubs(limit=12)
for hub in page["data"]: print(hub["id"], hub["slug"], hub["title"])const page = await api.listPublicHubs({ limit: 12 });
for (const hub of page.data) { console.log(hub.id, hub.slug, hub.title);}page, err := api.ListPublicHubs(ctx, 12, "")if err != nil { log.Fatal(err)}
for _, hub := range page.Data { fmt.Println(hub.ID, hub.Slug, hub.Title)}let page = api.list_public_hubs(Some(12), None).await?;
for hub in page.data { println!("{} {} {}", hub.id, hub.slug, hub.title);}Get One Public Hub
Section titled “Get One Public Hub”Use this when you have a hub reference from a URL, card, or saved choice.
hub = api.get_public_hub("joke-garden")print(hub["title"], hub["public_ref"])const hub = await api.getPublicHub("joke-garden");console.log(hub.title, hub.publicRef);hub, err := api.GetPublicHub(ctx, "joke-garden")if err != nil { log.Fatal(err)}fmt.Println(hub.Title, hub.PublicRef)let hub = api.get_public_hub("joke-garden").await?;println!("{} {}", hub.title, hub.public_ref);List Visible Hubs
Section titled “List Visible Hubs”Use this for hubs the signed-in workspace can see.
page = api.list_hubs(limit=25)
for hub in page["data"]: print(hub["id"], hub["title"])const page = await api.listHubs({ limit: 25 });
for (const hub of page.data) { console.log(hub.id, hub.title);}page, err := api.ListHubs(ctx, 25, "", "")if err != nil { log.Fatal(err)}
for _, hub := range page.Data { fmt.Println(hub.ID, hub.Title)}let page = api.list_hubs(Some(25), None, None).await?;
for hub in page.data { println!("{} {}", hub.id, hub.title);}Get One Hub
Section titled “Get One Hub”Use this after you already know the hub ID.
hub = api.get_hub("hub-id")print(hub["title"])const hub = await api.getHub("hub-id");console.log(hub.title);hub, err := api.GetHub(ctx, "hub-id")if err != nil { log.Fatal(err)}fmt.Println(hub.Title)let hub = api.get_hub("hub-id").await?;println!("{}", hub.title);Provision A Hub
Section titled “Provision A Hub”Use this when your code should create the hub. Discovery needs hubs:read and works on any plan. The four writes need hubs:write and a paid plan. For the etag rule, the immutable fields, and the error table, see Provision Hubs.
skills = api.list_marketplace_skills()["data"]
group = api.create_runtime_group({"name": "kiosks"})hub = api.create_hub({ "name": "joke-garden", "runtime_group_id": group["id"], "spec": {"protocols": {"wss": {"enabled": True}}},})
api.install_runtime_group_skill(group["id"], "skill-weather")api.release_runtime_group(group["id"], channel="stable")api.release_hub(hub["id"], channel="stable")const catalog = await api.listMarketplaceSkills();
const group = await api.createRuntimeGroup({ name: "kiosks" });const hub = await api.createHub({ name: "joke-garden", runtimeGroupId: group.id as string, spec: { protocols: { wss: { enabled: true } } },});
await api.installRuntimeGroupSkill(group.id as string, "skill-weather");await api.releaseRuntimeGroup(group.id as string, { channel: "stable" });await api.releaseHub(hub.id as string, { channel: "stable" });catalog, err := api.ListMarketplaceSkills(ctx, thalovant.MarketplaceSkillListOptions{})
group, err := api.CreateRuntimeGroup(ctx, map[string]any{"name": "kiosks"})groupID := group["id"].(string)
hub, err := api.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{})hubID := hub["id"].(string)
_, err = api.InstallRuntimeGroupSkill(ctx, groupID, "skill-weather", thalovant.RuntimeGroupSkillInstallOptions{})_, err = api.ReleaseRuntimeGroup(ctx, groupID, thalovant.ReleaseOptions{Channel: "stable"})_, err = api.ReleaseHub(ctx, hubID, thalovant.ReleaseOptions{Channel: "stable"})let catalog = api.list_marketplace_skills(MarketplaceSkillsOptions::default()).await?;
let group = api.create_runtime_group(json!({"name": "kiosks"})).await?;let group_id = group["id"].as_str().unwrap_or_default().to_string();
let hub = api .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();
api.install_runtime_group_skill(&group_id, "skill-weather", SkillInstallOptions::default()).await?;api.release_runtime_group(&group_id, ReleaseOptions { channel: Some("stable".into()), ..Default::default() }).await?;api.release_hub(&hub_id, ReleaseOptions { channel: Some("stable".into()), ..Default::default() }).await?;Kotlin, Swift, and .NET expose the same calls with native names. Their SDK pages show the language-specific form.
Update A Hub With Its Etag
Section titled “Update A Hub With Its Etag”Hub update and delete need the hub’s current etag, sent as If-Match. Read the hub first: the etag is a body field, and the API sends no ETag response header.
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("hub-id");hub = await api.updateHub("hub-id", { active: false }, { etag: hub.etag as string });await api.deleteHub("hub-id", { etag: hub.etag as string });hub, err := api.GetHub(ctx, "hub-id")hub, err = api.UpdateHub(ctx, "hub-id", map[string]any{"active": false}, hub["etag"].(string))err = api.DeleteHub(ctx, "hub-id", hub["etag"].(string))let hub = api.get_hub("hub-id").await?;let etag = hub["etag"].as_str().unwrap_or_default().to_string();
let hub = api.update_hub("hub-id", json!({"active": false}), &etag).await?;let etag = hub["etag"].as_str().unwrap_or_default().to_string();api.delete_hub("hub-id", &etag).await?;A missing or stale etag fails with HTTP 412 and changes nothing. Re-read the hub and retry.
Poll An Accepted Operation
Section titled “Poll An Accepted Operation”Use the operation ID returned by an asynchronous control-plane command. Stop at ready, failed, or timed_out; Git-only commands stop at committed.
operation = api.get_operation("operation-id")print(operation.status)const operation = await api.getOperation("operation-id");console.log(operation.status);operation, err := api.GetOperation(ctx, "operation-id")let operation = api.get_operation("operation-id").await?;See Operations for lifecycle and retry guidance.
Manage Skills On One Hub
Section titled “Manage Skills On One Hub”Use these to manage the skill attachments of the runtime group selected by a hub. Every hub sharing the runtime group is affected. Each write returns an operation; wait polls it every 2 seconds for up to 120 seconds by default. The writes need hubs:write and a paid plan; listing needs hubs:inspect, which hubs:read includes. Errors are problem JSON with a root code, such as skill_version_already_installed on HTTP 409 or hub_without_runtime_group on HTTP 404, and the SDK error text reads HTTP <status>: <message> (<code>). They arrive in Python SDK 0.5.15 and Node SDK 0.3.15. For the routes, the app path, and the error table, see Add a Skill to a Hub.
skills = api.list_hub_skills("hub-id") # HubSkillList; rows under .dataprint(skills.source, skills.observed_at)for skill in skills.data: print(skill.skill, skill.installed_version, skill.state, skill.update_available)
api.install_hub_skill("hub-id", "skill-weather", version="latest", wait=True)api.update_hub_skill("hub-id", "skill-weather", version="1.2.0", wait=True)api.remove_hub_skill("hub-id", "skill-weather", wait=True)const skills = await api.listHubSkills("hub-id"); // { data, source, observed_at, ... }console.log(skills.source, skills.observed_at);for (const skill of skills.data) { console.log(skill.skill, skill.installed_version, skill.state, skill.update_available);}
await api.installHubSkill("hub-id", "skill-weather", { version: "latest", wait: true });await api.updateHubSkill("hub-id", "skill-weather", { version: "1.2.0", wait: true });await api.removeHubSkill("hub-id", "skill-weather", { wait: true });The list call returns the route’s envelope, with the rows under data and each row carrying installed_version, latest_version, update_available, and a state of pending, installed, failed, removing, drifted, quarantined, or unmanaged. Each write returns the accepted operation with operation_id, skill, version, previous_version, and state. Go, Rust, Kotlin, Swift and .NET also provide these calls and history in their native naming style; see their SDK pages for wait/resume options.