Public discovery
List public hubs without requiring an access token.
The SDK contract defines the behavior each language SDK should expose, even when method names follow local language style.
Use this page when adding a new language SDK, reviewing a release, or checking whether an integration can move between SDKs.
| Area | Contract |
|---|---|
| Control plane | Discover public hubs, list authenticated hubs, sign in with optional MFA or through device login, accept a direct API token, poll operations with a typed status, and create client identities. |
| Provisioning | Browse the marketplace catalog, create and manage hubs and runtime groups, install skills, and apply releases. |
| Identity | Load the same identity fields in every SDK. |
| Protocols | Support WSS, HTTPS, and MQTT over TLS when the identity, the hub, and the SDK tier allow them. |
| Requests | Support natural language, action payloads, exact codes, sessions, and context. |
| Replies | Normalize text, speech, display items, policy state, and raw event data. |
The contract recognizes three tiers. A tier limits which parts of the contract an implementation must ship, never how correctly it ships them.
| Tier | Implementations | Contract scope |
|---|---|---|
| Full SDK | Python, Node.js, Go, Rust | The complete contract: full control plane plus the WSS, HTTPS, and MQTT data planes. |
| WSS-only SDK (v0.1) | Kotlin, Swift, .NET | The full control plane, including MFA sign-in, device login, direct token auth, and typed operation polling, with a WSS-only data plane. Requesting https or mqtt must raise a clear, typed unsupported-protocol error rather than failing while connecting. |
| Protocol library | Embedded C | Implements the wire, identity, and MQTT topic contracts byte-for-byte without owning a transport or a control plane. The integrator supplies the MQTT or WebSocket client, TLS, and RNG. |
Browser builds of the Node SDK follow the full-SDK contract minus MQTT and file loading: both must raise clear errors in a browser bundle rather than being silently absent.
Every SDK with a control plane implements these behaviors:
| Behavior | Contract |
|---|---|
| Sign in | POST /v1/auth/token with email and password; store the returned access token. |
| MFA sign-in | Accept optional otp_code and recovery_code inputs, sent only when set. Surface the API’s HTTP 401 mfa_required rejection with its error code intact. |
| Device login | POST /v1/auth/device/authorize with optional scopes and client name, prompt with the verification URI and user code through an overridable prompt, and best-effort open the browser. Poll POST /v1/auth/device/token honoring the server interval and the authorization_pending, slow_down, expired_token, and access_denied error codes, with a default timeout of 15 minutes. Store the returned durable scoped token exactly like a password login. |
| Direct token auth | Accept an API token in the control-plane constructor and send it as a bearer authorization header, with no login call. |
| Operation polling | Expose get_operation (native naming) returning a typed resource whose status is the OperationStatus set: requested, committed, applied, ready, failed, timed_out. |
| Identity provisioning | Create a client identity, honor the optional active flag (default true), and preserve returned protocol metadata. |
Every SDK with a control plane implements these behaviors. Each row below is present in all seven language SDKs.
| Behavior | Contract |
|---|---|
| Catalog discovery | List marketplace skills with optional force_refresh. Needs hubs:read only, and must not be gated behind a paid plan in docs or in code paths. |
| Group discovery | List runtime groups, get one runtime group, and read its config. |
| Group inspection | Read a runtime group’s observed skill inventory and its resolved marketplace view, each with an optional refresh flag. Preserve the installable, purchase_required, and access_message fields so callers can check access before installing. |
| Hub lifecycle | Create, get, update, and delete a hub. Create accepts an optional idempotency key and generates one when the caller omits it. |
| Optimistic locking | Update and delete require the hub’s current etag as a mandatory, non-defaulted parameter, sent as If-Match. The etag is read from the etag field of the hub resource body, because the API sends no ETag response header. |
| Immutable fields | Do not offer name, namespace, or domain as changeable inputs on hub update. The API rejects a change with HTTP 400. |
| Group lifecycle | Create, get, update, and delete a runtime group. No runtime group route sends If-Match, and runtime group resources carry no etag. |
| Skill install | Install and uninstall a skill in a runtime group. Install defaults source_type to catalog and active to true. |
| Release | Apply a release to a hub and to a runtime group as two separate calls, each accepting channel, mode, version, images, and reason. |
| Ratings | Set and clear the caller’s rating on a public hub. These need hubs:write but no paid plan. |
Provisioning writes need hubs:write and a paid plan. The API checks scope before plan, so SDK docs should describe HTTP 403 as a token problem and HTTP 402 as a workspace problem, and should not present 402 as the free-tier failure for a token that cannot hold the scope.
Provisioning calls return the API’s JSON resource. No SDK is required to expose typed hub or runtime group models.
Error surfaces differ by tier today. The Kotlin, Swift, and .NET SDKs expose the HTTP status on their API error type. The Python, Node.js, Go, and Rust SDKs carry the status inside the error message text instead. New SDKs should follow the Kotlin, Swift, and .NET shape.
Public discovery
List public hubs without requiring an access token.
Authenticated hubs
List hubs visible to the signed-in workspace when API access is allowed.
Identity provisioning
Create a client identity and preserve returned protocol metadata.
Protocol selection
Select WSS, HTTPS, or MQTT explicitly and fail clearly when unavailable.
Health
Check that the endpoint, identity, and transport are usable.
Ask
Send one request and return a normalized reply object.
Action
Send a structured command or UI selection without turning it into prose.
Code
Send exact input such as scanned values, serial numbers, or typed codes.
Every SDK should understand these fields:
| Field | Meaning |
|---|---|
access_key |
Client identifier used during hub auth. |
password |
Client secret used during hub auth. |
crypto_key |
Optional shared payload encryption key. |
site_id |
Stable client, service, device, or runtime label. |
data_plane_endpoints |
Explicit WSS, HTTPS, and MQTT endpoints. |
protocols |
Hub protocol flags returned with the identity. |
mqtt |
Per-client MQTT broker credentials and topic scope. |
SDKs should load ~/.config/thalovant/config.yaml by default for local development, honor XDG_CONFIG_HOME, support named profiles, and reject group-readable or world-readable config files on Linux and macOS.
Every SDK should expose these concepts:
| Field | Meaning |
|---|---|
text |
Plain fallback answer for chat, logs, and tests. |
speech |
Speakable answer text after markup cleanup. |
display_items |
Structured UI items such as choices, tables, images, and attachments. |
session_id |
Session used by the request or returned by the hub. |
request_id |
Request trace key when provided or generated. |
policy_denied |
True when access policy blocked the request. |
raw_event |
Original event payload for advanced integrations. |
| Rule | Why it matters |
|---|---|
| Unknown identity fields should be ignored. | New fields must not break older clients. |
| Unknown display items should be ignored. | New UI outputs must not break older apps. |
| Missing optional metadata should use defaults. | Small clients should not need every field. |
| Method names should feel native. | Every SDK, from Python through C#, should match its language norms. |
| Error objects should include context. | Callers need endpoint, status, request ID, and policy details when available. |
| Runtime protocol defaults should be predictable. | Prefer WSS, then HTTPS, then MQTT when broker credentials exist. |
| MQTT requires client-scoped credentials. | Broker access must stay limited to one client and its topics. |
| Area | Requirement |
|---|---|
| API tokens | Send only as authorization headers. Do not put tokens in URLs. |
| Identity output | Redact passwords, broker passwords, and crypto keys by default. |
| Local config | Require owner-only permissions for ~/.config/thalovant/config.yaml on Linux and macOS. |
| TLS | Honor mqtt.tls: true even when the endpoint scheme is not already mqtts://. |
| Public endpoints | Prefer https://, wss://, and mqtts:// in examples and generated identities. |
| MQTT topics | Use returned client topics or derive topics inside the client scope only. |
| Timeouts | Bound connect, send, and wait operations so clients fail clearly. |
| Rule | Why it matters |
|---|---|
| Code changes require a version bump. | CI publishes existing versions, it does not invent new ones. |
| Tests cover protocol selection. | Every SDK must reject unavailable protocols clearly. |
| Tests cover secret redaction. | SDKs must not leak credentials in normal summaries. |
| Docs update with new features. | Users should not need source code to discover supported behavior. |
| Capability | Python | Node | Go | Rust |
|---|---|---|---|---|
| Ask | ask |
ask |
Ask |
ask |
| Context | build_client_context |
buildClientContext |
BuildClientContext |
build_client_context |
| Action | send_action |
sendAction |
SendAction |
send_action |
| Code | send_code |
sendCode |
SendCode |
send_code |
| Protocol check | supports_protocol |
supportsProtocol |
SupportsProtocol |
supports_protocol |
| MFA sign-in | login(otp_code=, recovery_code=) |
login(..., { otpCode, recoveryCode }) |
LoginWithOptions |
login_with_options |
| Device login | login_with_browser |
loginWithBrowser |
LoginWithBrowser |
login_with_browser |
| Get operation | get_operation |
getOperation |
GetOperation |
get_operation |
| List marketplace skills | list_marketplace_skills |
listMarketplaceSkills |
ListMarketplaceSkills |
list_marketplace_skills |
| Create hub | create_hub |
createHub |
CreateHub |
create_hub |
| Update hub | update_hub(..., etag=) |
updateHub(..., { etag }) |
UpdateHub(..., etag) |
update_hub(..., etag) |
| Create runtime group | create_runtime_group |
createRuntimeGroup |
CreateRuntimeGroup |
create_runtime_group |
| Install skill | install_runtime_group_skill |
installRuntimeGroupSkill |
InstallRuntimeGroupSkill |
install_runtime_group_skill |
| Release hub | release_hub |
releaseHub |
ReleaseHub |
release_hub |
Kotlin, Swift, and C# follow the same native-naming rule: login(otpCode = ...), loginWithBrowser, getOperation in Kotlin; login(otpCode:), loginWithBrowser(options:), getOperation(id:) in Swift; LoginAsync(otpCode: ...), LoginWithBrowserAsync, GetOperationAsync in C#. Their SDK pages show the full surface.
The provisioning rows follow the same rule: createHub and updateHub(hubId, payload, etag) in Kotlin; createHub(_:idempotencyKey:) and updateHub(_:_:etag:) in Swift; CreateHubAsync and UpdateHubAsync(hubId, options, etag) in C#.