@tangle-network/hub-sdk
v0.10.21
Published
Typed SDK for Tangle Hub tool discovery and execution
Downloads
56,333
Readme
@tangle-network/hub-sdk
Typed SDK for the Tangle Hub /v1/hub/* surface. Status, connections, tools
discovery and invocation, capability tokens, policies, approvals, audit,
inbound channels, and product event subscriptions.
Install
pnpm add @tangle-network/hub-sdkQuick start
import { HubClient } from "@tangle-network/hub-sdk";
const hub = HubClient.fromEnv();
const { principal, connections } = await hub.status();HubClient.fromEnv() reads everything it needs from environment variables.
SaaS vs on-prem flips via env only — there is no per-client default URL.
Environment variables
| Variable | Required | Meaning |
| ------------------------------ | -------- | --------------------------------------------------------------------------------------------- |
| TANGLE_HUB_URL | Yes | Hub root URL. Either the platform root (https://api.tangle.tools) or the full Hub URL including /v1/hub — both are accepted; the SDK normalizes. |
| TANGLE_API_KEY | One of | Long-lived API key (Bearer). User / API key principal. |
| TANGLE_HUB_CAPABILITY_TOKEN | One of | Short-lived capability token (Bearer). Sandbox-runtime principal. Auto-minted server-side per action. |
The two auth credentials are mutually exclusive — set exactly one. This
mirrors the platform-api hubSandboxEnvironmentSchema rule
(exactly-one-api-key-or-capability-token).
Examples
SaaS:
export TANGLE_HUB_URL=https://api.tangle.tools
export TANGLE_API_KEY=sk-tan-...On-prem:
export TANGLE_HUB_URL=https://hub.acme.internal
export TANGLE_API_KEY=sk-tan-...Inside a sandbox (capability-token principal):
export TANGLE_HUB_URL=https://api.tangle.tools/v1/hub
export TANGLE_HUB_CAPABILITY_TOKEN=hubcap_...Fail-loud configuration
HubClient.fromEnv() (and the standalone resolvers resolveHubBaseUrl() /
resolveHubAuth()) never default. Missing or malformed configuration
throws HubSdkError synchronously, before any request is issued:
| Code | Cause |
| --------------------- | -------------------------------------------------------------------------------- |
| HUB_CONFIG_MISSING | TANGLE_HUB_URL unset/empty, or neither auth credential is set. |
| HUB_CONFIG_INVALID | TANGLE_HUB_URL is not a valid URL, or both auth credentials are set. |
try {
const hub = HubClient.fromEnv();
} catch (error) {
if (error instanceof HubSdkError && error.code === "HUB_CONFIG_MISSING") {
// Surface the missing env var to the operator. Do not retry, do not default.
}
throw error;
}This matches the repository's "No fallbacks. Fail loud." doctrine: required config has no silent default.
Bypassing env auth resolve
Pass apiKey or authHeaders explicitly to skip the auth-env-var check
(useful when credentials come from a vault, OIDC exchange, or a custom
session token). TANGLE_HUB_URL is still required.
const hub = HubClient.fromEnv({
apiKey: await vault.read("hub/api-key"),
});
const hub = HubClient.fromEnv({
authHeaders: async () => ({ Authorization: `Bearer ${await sessionToken()}` }),
});Constructing without env vars
new HubClient({...}) remains supported for callers that resolve config
themselves (CLI flags, web app config, tests). fromEnv is sugar with a
fail-loud contract on top.
const hub = new HubClient({
baseUrl: "https://api.tangle.tools",
apiKey: "sk-tan-...",
});Browsers, Workers, edge runtimes
fromEnv() reads globalThis.process?.env — undefined in browsers and many
edge runtimes. Pass an explicit env map there:
const hub = HubClient.fromEnv({
env: {
TANGLE_HUB_URL: import.meta.env.VITE_TANGLE_HUB_URL,
TANGLE_API_KEY: await session.apiKey(),
},
fetch: globalThis.fetch,
});Workflows
hub.workflows covers the /v1/workflows surface — the same resource the
platform web UI and the tangle workflows CLI drive. It authenticates with the
sk-tan-* API key (or a session), not a hub capability token. Alongside
CRUD (list/get/create/update/delete), setEnabled, validate, and
schema, it can trigger and observe runs:
const hub = HubClient.fromEnv();
// Trigger a run with the trigger fields the workflow reads, then wait for it.
const { runId } = await hub.workflows.run("wf_123", {
"pull_request.number": "123",
});
const run = await hub.workflows.waitForRun("wf_123", runId, {
timeoutMs: 300_000,
});
console.log(run.status, run.actionResults);
// Or tail live progress as it executes.
for await (const event of hub.workflows.watchRun("wf_123", runId)) {
if (event.type === "token") process.stdout.write(event.delta);
if (event.type === "run.done") console.log("\n", event.status);
}
// One page of run history + a single run's full detail.
const { runs, nextCursor } = await hub.workflows.listRuns("wf_123");
const detail = await hub.workflows.getRun("wf_123", runs[0].id);run throws HubSdkError with MISSING_RUN_INPUTS (the workflow reads trigger
fields none were supplied for — details.missing names them) or
WORKFLOW_DISABLED (enable it first). waitForRun throws
WORKFLOW_RUN_TIMEOUT if the run has not finished within timeoutMs. Live
watchRun ticks require the worker executing the run to share the API process;
across instances only snapshot + run.done arrive, and the persisted record
read via getRun stays the source of truth.
Product event subscriptions
Products can bind a managed channel or a hosted provider connection to a signed server callback without authoring workflow YAML:
OAuth readiness and inbound-event readiness are separate.
Check provider.eventIngressConfigured === true before presenting an inbound
channel as available.
An absent value means the server predates readiness discovery and should be
treated as unavailable.
const callbackSecret = await deriveHubEventCallbackSecret({
rootSecret: env.HUB_EVENT_CALLBACK_ROOT_SECRET,
productId: "relationships",
ownerId: tangleUserId,
bindingId: channel.id,
});
const { subscription } = await hub.eventSubscriptions.create({
clientReference: `relationships:${channel.id}`,
label: "Relationship workspace inbound",
source: { type: "channel", channelId: channel.id },
event: "email.received",
callback: {
url: "https://relationships.example/api/events",
secret: callbackSecret,
},
});clientReference is the product-owned idempotency key.
The callback URL and secret are encrypted by the platform and never returned.
The derivation scopes one callback secret to one product, owner, and binding, so
the product stores only those existing ids.
On receipt, authenticate and parse the exact raw request in one call:
const authenticated = await authenticateHubEventRequest({
request,
secret: callbackSecret,
});
if (!authenticated.ok) return authenticated.response;
const { delivery } = authenticated;
const email = delivery.providerEvent.payload;The signed delivery includes the verified provider event directly, so the
product does not need a second workflow-run request.
Callbacks may be retried; use delivery.runId as the durable idempotency key
before starting product work.
Exports
HubClient,HubClient.fromEnv(options?)HubConnectionsClient,HubPermissionsClient,HubTokensClient,HubChannelsClient,HubEventSubscriptionsClient,HubToolsClient,HubApprovalsClient,HubAuditClient,HubWorkflowsClientderiveHubEventCallbackSecret,authenticateHubEventRequest,verifyHubEventSignature,parseHubEventDelivery,HubEventDeliveryErrorHubSdkError— typedcode: HubErrorCode, redacteddetails, optional HTTPstatusHUB_URL_ENV_VAR,HUB_API_KEY_ENV_VAR,HUB_CAPABILITY_TOKEN_ENV_VAR— string constants for the env-var names (mirrored by the platform-apihub-sandbox-contract.tsz.literal(...))resolveHubBaseUrl(env?),resolveHubAuth(env?)— standalone resolvers with the same fail-loud contract asfromEnv- All request/response types from the
/v1/hub/*contract
