Skip to content
Console

SDK Contract

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