XKOVA Docs

SDK Overview

The TypeScript SDK is the cleanest abstraction over the API. It is two layers: a low level client that mirrors the operations one to one, and a workflow client that wraps the multi step flows as a single call each. It runs server side and never holds a member key.

Installation

The SDK ships as the @xkova/sdk package on npm, with source and issues at github.com/XKOVA/sdk. It requires Node 22+ and ships its own TypeScript types.

npm install @xkova/sdk

Use it from a server, backend for frontend, worker, or other trusted backend process. Do not put tenant API keys in a browser.

Member browser flows use hosted UI handoff or member scoped credentials, never a tenant API key. See Authentication for the member identity patterns.

Construct a client by scope

The client is created through a scope aware constructor so the right credential and headers are wired for you.

ConstructorUse it for
XKOVAClient.forTenant({ baseUrl, apiKey })Server to server calls scoped to one tenant with a tenant key. This is the constructor integrators use.
XKOVA also runs an internal platform scope that sits above individual tenants, used by XKOVA's own platform and hosted tooling. Integrators do not use it, so the tenant client above is the SDK's integrator surface.
import { XKOVAClient, XKOVAApiError } from '@xkova/sdk';

const client = XKOVAClient.forTenant({
  baseUrl: 'https://sandbox-api.xkova.com',
  apiKey: process.env.XKOVA_API_KEY,
});

const chains = await client.listChains();

Workspace scope

A tenant API key is scoped to a single workspace on the server, so every call a tenant client makes already runs in that workspace and you do not select one from the client. A tenant that operates more than one workspace, for example a sandbox workspace alongside a production workspace, provisions one key per workspace and constructs one client per key.

Low level client: client.namespace.method()

Each namespace is typed one to one from the operations, and method names follow a verb plus resource convention. Field names stay snake_case because the SDK mirrors the JSON wire contract.

const payments = await client.payments.list({ limit: 20 });
const token = await client.tokenization.deployErc20({ /* ... */ });
const cap = await client.rwa.capTable('ctk_...');

The flat client is the money movement surface and covers namespaces such as payments, escrows, claims, paymentRequests, transactions, contacts, accountHolders, tokenization, rwa, ramps, wallets, webhooks, feeSchedules, branding, memberAuth, tenant, workspaces, workspaceTokens, onboarding, integrationGateways, and chains.

Operator surface: client.operator.namespace.method()

The administrative namespaces are grouped under client.operator so the money movement client stays focused. They include treasury, compliance, governance, roles, policies, revenue, reports, apiKeys, team, accounts, entitlements, breakGlass, auth, and me (the operator's own profile and scopes).

const cases = await client.operator.compliance.listCases();
const vaults = await client.operator.treasury.listWallets();
Billing is not part of the SDK. It is managed in the XKOVA console.

Workflow client

The workflow client wraps a multi step outcome as one call. The non custodial member signature is a callback you provide (the SDK never holds member keys), and terminal status is polled for you. See SDK Workflows for the full set.

Standalone helpers

HelperWhat it does
withRetry(fn, policy)Retries using the caller-supplied delaysMs schedule (with jitter) and the isRetryable predicate. See Errors and Correlation IDs.
withIdempotency()Generates an idempotency key in both shapes: pass the object straight as a mutation's options (it carries idempotencyKey), or spread its headers into a raw fetch. Reuse the same object to make a retry replay. See Idempotency.
isHeldApproval() / assertNotHeld()Narrows a governance-held mutation response (status: pending_approval) before you read entity fields; assertNotHeld throws a typed error carrying the approval request id. See Policies and Quorum Governance.
paginateCursor() / collectCursor()Walks a cursor paginated list to exhaustion. See Pagination.
verifyWebhookSignature()Verifies the HMAC-SHA256 XKOVA-Webhook-Signature header over a raw webhook body. See Webhooks.
canonicalJson(value)Sorted key, whitespace free JSON for client side body fingerprints.

Errors and types

Non 2xx responses throw XKOVAApiError, which carries code, message, details, and correlationId (the wire's correlation_id, exposed in camelCase: read err.correlationId). Branch on err.code for control flow. The package also exports generated TypeScript types mirroring the wire shape, regenerated from the contract.

Governance holds

On a workspace with a governance requirement (fresh workspaces seed several, see Policies and Quorum Governance), a create-shaped mutation may return a hold instead of the entity. Narrow before reading entity fields:

import { isHeldApproval } from '@xkova/sdk';

const res = await client.operator.treasury.createCounterparty(body, opts);
if (isHeldApproval(res)) {
  // No entity created yet: the owner attests res.pending_approval_request_id,
  // then the SAME call is re-issued and performs the create.
} else {
  res.id; // the entity
}

Scripts that treat a hold as exceptional can use assertNotHeld(res), which throws a typed error carrying the approval request id. One shape differs: rotating an API key echoes the unchanged key with pending_approval_request_id set when held, so check that field on rotate responses.

Related

For the flows the workflow client wraps, see SDK Workflows. For the raw operations, see the interactive reference. For copy ready integration paths, see Recipes and Examples.