# Finding and provisioning hubs

> List hubs, read one, create one, update it, and follow the operation that results.

Source: https://docs.thalovant.com/developers/sdk-functions/finding-hubs/

---
Part of [SDK Functions](/developers/sdk-functions/). Every call below is shown in each language that has it.

## 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.

  

```python
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"
```

  
  

```ts

hubDisplayName({ slug: "kitchen-hub" }); // "Kitchen Hub"
```

  
  

```go
thalovant.HubDisplayName(map[string]any{"slug": "kitchen-hub"}) // "Kitchen Hub"
```

  
  

```rust
thalovant::hubs::hub_display_name(&hub) // "Kitchen Hub"
```

  
  

```kotlin
hubDisplayName(hub) // "Kitchen Hub"
```

  
  

```swift
hubDisplayName(hub) // "Kitchen Hub"
```

  
  

```csharp
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

Use public hub discovery before you ask a user to choose a hub.

  

```python
page = api.list_public_hubs(limit=12)

for hub in page["data"]:
    print(hub["id"], hub["slug"], hub["title"])
```

  
  

```ts
const page = await api.listPublicHubs({ limit: 12 });

for (const hub of page.data) {
  console.log(hub.id, hub.slug, hub.title);
}
```

  
  

```go
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)
}
```

  
  

```rust
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

Use this when you have a hub reference from a URL, card, or saved choice.

  

```python
hub = api.get_public_hub("joke-garden")
print(hub["title"], hub["public_ref"])
```

  
  

```ts
const hub = await api.getPublicHub("joke-garden");
console.log(hub.title, hub.publicRef);
```

  
  

```go
hub, err := api.GetPublicHub(ctx, "joke-garden")
if err != nil {
	log.Fatal(err)
}
fmt.Println(hub.Title, hub.PublicRef)
```

  
  

```rust
let hub = api.get_public_hub("joke-garden").await?;
println!("{} {}", hub.title, hub.public_ref);
```

  

## List Visible Hubs

Use this for hubs the signed-in workspace can see.

  

```python
page = api.list_hubs(limit=25)

for hub in page["data"]:
    print(hub["id"], hub["title"])
```

  
  

```ts
const page = await api.listHubs({ limit: 25 });

for (const hub of page.data) {
  console.log(hub.id, hub.title);
}
```

  
  

```go
page, err := api.ListHubs(ctx, 25, "", "")
if err != nil {
	log.Fatal(err)
}

for _, hub := range page.Data {
	fmt.Println(hub.ID, hub.Title)
}
```

  
  

```rust
let page = api.list_hubs(Some(25), None, None).await?;

for hub in page.data {
    println!("{} {}", hub.id, hub.title);
}
```

  

## Get One Hub

Use this after you already know the hub ID.

  

```python
hub = api.get_hub("hub-id")
print(hub["title"])
```

  
  

```ts
const hub = await api.getHub("hub-id");
console.log(hub.title);
```

  
  

```go
hub, err := api.GetHub(ctx, "hub-id")
if err != nil {
	log.Fatal(err)
}
fmt.Println(hub.Title)
```

  
  

```rust
let hub = api.get_hub("hub-id").await?;
println!("{}", hub.title);
```

  

## 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](/developers/provisioning/).

  

```python
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")
```

  
  

```ts
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" });
```

  
  

```go
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"})
```

  
  

```rust
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

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.

  

```python
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"])
```

  
  

```ts
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 });
```

  
  

```go
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))
```

  
  

```rust
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

Use the operation ID returned by an asynchronous control-plane command. Stop at `ready`, `failed`, or `timed_out`; Git-only commands stop at `committed`.

  

```python
operation = api.get_operation("operation-id")
print(operation.status)
```

  
  

```ts
const operation = await api.getOperation("operation-id");
console.log(operation.status);
```

  
  

```go
operation, err := api.GetOperation(ctx, "operation-id")
```

  
  

```rust
let operation = api.get_operation("operation-id").await?;
```

  

See [Operations](/developers/operations/) for lifecycle and retry guidance.

## 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](/manage/add-a-skill-to-a-hub/).

  

```python
skills = api.list_hub_skills("hub-id")  # HubSkillList; rows under .data
print(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)
```

  
  

```ts
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.
