TypeScript SDK reference
This page documents the public TypeScript surface shipped in kit/ts and the shared signing rules from crates/paybond-kit. For narrative walkthroughs, start with the TypeScript quickstart.
Every Kit method that talks to Harbor is backed by an OpenAPI operation. The canonical contract lives in docs/api/harbor-openapi.yaml; operation ids used below are linked via the rendered Harbor API reference with anchors like #operation/harborCreateIntent.
Scope note: this SDK currently wraps Harbor execution flows, tenant-scoped ledger provenance reads, and read-only tenant-bound Signal reads.
Module entry point
import {
Paybond,
PaybondCapabilityBinding,
PaybondIntents,
ServiceAccountHarborSession,
ServiceAccountSignalSession,
HarborClient,
GatewaySignalClient,
GatewayHarborTokenProvider,
HarborHttpError,
GatewayAuthError,
SignalHttpError,
// low-level helpers
buildSignedCreateIntentBody,
intentCreationSignBytesRaw,
signPayeeEvidenceBinding,
artifactsDigest,
normalizeJson,
jsonValueDigest,
} from "@paybond/kit";High-level client
Paybond.open
static Paybond.open(init: {
gatewayBaseUrl: string;
apiKey: string; // "paybond_sk_…" service-account key
harborBaseUrl: string;
harborAccessPath?: string; // default "/v1/auth/harbor-access"
clockSkewSeconds?: number; // default 90
maxRetries?: number; // default 3
}): Promise<Paybond>;Exchanges the service-account key at the Gateway (POST /v1/auth/harbor-access), caches the echoed tenant_id, and returns a Paybond instance bound to that tenant realm. Underneath it constructs a GatewayHarborTokenProvider and a HarborClient.
Throws GatewayAuthError on any token-exchange failure. Response body text is redacted only by you at log time — do not log raw JWTs.
Tenant isolation: the returned session is bound to the Gateway-derived tenant_id. Do not reuse the instance across tenants — build one per (tenant, service account).
Paybond instance
class Paybond {
readonly harbor: HarborClient; // tenant-bound low-level client
readonly intents: PaybondIntents; // ergonomic intent helpers
rotateHarborToken(): Promise<void>;
aclose(): Promise<void>;
}paybond.harbor— the tenant-boundHarborClient. Use for operations the high-levelintentshelper does not wrap (for exampleverifyCapability).paybond.intents— seePaybondIntents.paybond.rotateHarborToken()— force the Gateway exchange to rotate the cached Harbor JWT (credential rotation drills).paybond.aclose()— release HTTP resources. Always await in afinallyblock.
PaybondIntents
paybond.intents wraps the two principal/payee signing flows. Both methods derive tenant scope from the bound Harbor client; callers never pass a tenant id.
paybond.intents.create
paybond.intents.create(params: {
principalDid: string;
principalSigningSeed: Uint8Array; // exactly 32 bytes
payeeDid: string;
budget: unknown; // canonical JSON (e.g. { currency, max_spend_usd })
predicate: unknown; // predicate_dsl (version 1 and root node)
currency: string;
amountCents: number;
evidenceSchema: unknown; // JSON schema for evidence payloads
deadlineRfc3339: string;
allowedTools: string[];
predicateRef?: string; // managed-policy digest (v3) when set
intentId?: string; // defaults to crypto.randomUUID()
idempotencyKey?: string;
}): Promise<Record<string, unknown>>;Builds a principal-signed POST /intents body via buildSignedCreateIntentBody, applies BLAKE3 digests to canonical JSON for budget, evidence_schema, and predicate_dsl, and POSTs it through HarborClient.createIntent. Maps to Harbor operation harborCreateIntent.
Retries honor idempotencyKey on transient 5xx/429. Reusing a key with a different body returns HTTP 409 from Harbor (see docs/api/harbor-idempotency-openapi.yaml).
allowedTools is a caller-defined allow-list of delegated operation names such as travel.planner.plan or crm.contact.enrich. Paybond does not define these strings; Harbor only checks that later verify calls use a matching value.
paybond.intents.submitEvidence
paybond.intents.submitEvidence(params: {
intentId: string;
payeeDid: string;
payeeSigningSeed: Uint8Array; // exactly 32 bytes
payload: unknown; // evidence JSON matching the intent's schema
artifactsBlake3Hex?: string[];
submittedAtRfc3339?: string; // defaults to now()
idempotencyKey?: string;
}): Promise<SubmitEvidenceResult>;Signs the payee binding via signPayeeEvidenceBinding and POSTs it through HarborClient.submitEvidence. Maps to Harbor operation harborSubmitEvidence.
The response includes state, tenant, and an optional predicatePassed boolean; a false here is not an HTTP error — the predicate evaluation failed and the intent moved to evidence_submitted with the recorded failure.
Verify (pass-through)
The Kit exposes capability verification on the underlying Harbor client rather than on paybond.intents:
paybond.harbor.verifyCapability({
intentId: string;
token: string; // base64 Biscuit token
operation: string; // e.g. "payments.capture"
requestedSpendCents?: number; // default 0
}): Promise<VerifyCapabilityResult>;Maps to Harbor operation harborVerifyCapability. Returns a 200 with allow: false when Harbor denies — that is not thrown as an error.
Confirm settlement (not wrapped)
Settlement confirmation is an operator-tier action today and is not wrapped by the Kit. Call Harbor directly for operation harborConfirmSettlement:
const res = await fetch(`${harborBase}/intents/${intentId}/settlement/confirm`, {
method: "POST",
headers: {
authorization: `Bearer ${await harborBearer()}`,
"content-type": "application/json",
"x-tenant-id": paybond.harbor.tenantId,
"idempotency-key": settlementIdempotencyKey,
},
body: JSON.stringify({ outcome: "release" }), // or { outcome: "refund" }
});Prefer the Operator Console for human-audited settlement.
Low-level session
ServiceAccountHarborSession
class ServiceAccountHarborSession {
static open(init: ServiceAccountHarborSessionInit): Promise<ServiceAccountHarborSession>;
readonly harbor: HarborClient;
rotateHarborToken(): Promise<void>;
aclose(): Promise<void>;
}Same lifecycle as Paybond.open without the ergonomic intents helper. Use it when you only need raw HarborClient methods (for example verify flows in agent guardrails).
GatewayHarborTokenProvider
class GatewayHarborTokenProvider {
constructor(init: {
gatewayBaseUrl: string;
apiKey: string;
harborAccessPath?: string;
clockSkewSeconds?: number;
clock?: () => number;
});
readonly tenantId: string | null;
ensureInitial(): Promise<string>; // returns tenant realm
bearer(): Promise<string>; // valid Harbor JWT (auto-refresh)
forceRotate(): Promise<void>;
}Owns the cached Gateway exchange. Tests and custom runtimes can inject a deterministic clock. Missing tenant_id or access_token in the Gateway response throws GatewayAuthError (requires Gateway V1-008+).
HarborClient
Tenant-bound async HTTP client. Sends x-tenant-id on every request; verifies Harbor echoes the same tenant and the requested intent_id on POST /verify (confused-deputy hardening).
class HarborClient {
constructor(harborBase: string, tenantId: string, options?: {
harborBearerSupplier?: () => Promise<string | null | undefined>;
staticHarborBearerToken?: string;
maxRetries?: number;
});
readonly tenantId: string;
verifyCapability(input: {
intentId: string;
token: string;
operation: string;
requestedSpendCents?: number;
}): Promise<VerifyCapabilityResult>; // harborVerifyCapability
createIntent(
body: Record<string, unknown>,
options?: { idempotencyKey?: string },
): Promise<Record<string, unknown>>; // harborCreateIntent
submitEvidence(
intentId: string,
evidenceBody: Record<string, unknown>,
options?: { idempotencyKey?: string },
): Promise<SubmitEvidenceResult>; // harborSubmitEvidence
getLedgerTip(): Promise<Record<string, unknown>>;
getLedgerAuthority(): Promise<Record<string, unknown>>;
getLedgerEvents(options?: { afterSeq?: number; limit?: number }): Promise<Record<string, unknown>>;
getLedgerMerkleLatest(): Promise<Record<string, unknown>>;
}Retries 429 / 500 / 502 / 503 / 504 with exponential backoff and jitter, honoring a bounded Retry-After. All other 4xx/5xx throw HarborHttpError.
Ledger reads
The SDK includes tenant-scoped provenance reads on HarborClient:
getLedgerTip()→GET /ledger/v1/tipgetLedgerAuthority()→GET /ledger/v1/authoritygetLedgerEvents({ afterSeq, limit })→GET /ledger/v1/eventsgetLedgerMerkleLatest()→GET /ledger/v1/merkle/latest
Each method validates the echoed tenant_id against the bound client tenant and fails closed on mismatch.
Signal reads
The TypeScript SDK ships a tenant-bound gateway Signal client for the supported read-only V1.0.1 surfaces.
GatewaySignalClient
class GatewaySignalClient {
constructor(gatewayBaseUrl: string, tenantId: string, options: {
staticGatewayBearerToken: string;
maxRetries?: number;
});
readonly tenantId: string;
getReputationReceipt(
operatorDid: string,
scoreVersion?: string,
): Promise<Record<string, unknown> | null>;
getPortfolioSummary(scoreVersion?: string): Promise<Record<string, unknown>>;
getOperatorExplanation(
operatorDid: string,
scoreVersion?: string,
): Promise<Record<string, unknown> | null>;
getOperatorReviewStatus(
operatorDid: string,
scoreVersion?: string,
): Promise<Record<string, unknown> | null>;
}This client validates the echoed tenant_id and operator_did on every response. 404 remains a null result for receipt, explanation, and review-status lookups; other non-success statuses throw SignalHttpError.
ServiceAccountSignalSession
class ServiceAccountSignalSession {
static open(init: {
gatewayBaseUrl: string;
apiKey: string;
principalPath?: string; // default "/v1/auth/principal"
maxRetries?: number;
}): Promise<ServiceAccountSignalSession>;
readonly signal: GatewaySignalClient;
aclose(): Promise<void>;
}This is the easiest tenant-safe path for partner developers who only need Signal reads. The session resolves the tenant from GET /v1/auth/principal using the same service-account credential instead of relying on ad hoc tenant headers.
PaybondCapabilityBinding
class PaybondCapabilityBinding {
constructor(
readonly harbor: HarborClient,
readonly intentId: string,
readonly capabilityToken: string,
);
}Binds (harbor, intentId, capabilityToken) so agent tool wrappers have a single tenant-safe handle to verify against. See the Capabilities guide and the verify walkthrough.
Result types
VerifyCapabilityResult
type VerifyCapabilityResult = {
allow: boolean;
auditId: string;
tenant: string;
intentId: string;
code?: string;
message?: string;
};SubmitEvidenceResult
type SubmitEvidenceResult = {
intentId: string;
tenant: string;
state: string;
predicatePassed?: boolean;
};Canonical signing helpers
These are exported for advanced callers that build CreateIntentRequest bodies or evidence payloads outside of paybond.intents. They match the canonical signing rules in crates/paybond-kit byte-for-byte.
buildSignedCreateIntentBody
buildSignedCreateIntentBody(params: BuildSignedCreateIntentParams): Record<string, unknown>;Builds a signed CreateIntentRequest body for operation harborCreateIntent. Canonical JSON normalisation (key sorting, number canonicalisation, stripped whitespace) is applied before BLAKE3 digesting and Ed25519 signing.
intentCreationSignBytesRaw
intentCreationSignBytesRaw(params: BuildSignedCreateIntentParams): Uint8Array;Returns the exact bytes that get Ed25519-signed. Useful for offline signers (for example HSMs or air-gapped principal keys).
signPayeeEvidenceBinding
signPayeeEvidenceBinding(params: SignPayeeEvidenceParams): Record<string, unknown>;Builds a signed evidence wire body for operation harborSubmitEvidence.
artifactsDigest, normalizeJson, jsonValueDigest
BLAKE3-based helpers that match the canonical rules used by Harbor. Exported for offline verification tooling.
Typed errors
All error classes extend Error and attach structured diagnostics instead of wrapping an opaque unknown. Never log raw tokens or API keys; the Kit redacts nothing itself — redaction is a caller responsibility.
HarborHttpError
class HarborHttpError extends Error {
readonly statusCode: number;
readonly url: string;
readonly bodyText: string; // raw Harbor response body (may be JSON)
}Thrown by HarborClient when Harbor returns any non-success status after retries. Look for the machine-readable code field inside bodyText — contracts are in the Error envelope reference.
GatewayAuthError
class GatewayAuthError extends Error {
readonly statusCode: number | undefined;
readonly bodyText: string | undefined;
}Thrown when /v1/auth/harbor-access rejects the API key, returns malformed JSON, or omits access_token / tenant_id. A missing tenant_id is logged as PAYBOND-V1-008 — upgrade the Gateway if you see it.
SignalHttpError
class SignalHttpError extends Error {
readonly statusCode: number;
readonly url: string;
readonly bodyText: string;
}Thrown by GatewaySignalClient when gateway Signal routes return a non-success status after retries, excluding the 404 -> null read contract on receipt, explanation, and review-status lookups.
Tenant/intent mismatch
HarborClient.verifyCapability throws a plain Error with "verify tenant mismatch" or "verify intent mismatch" if Harbor echoes a different realm or intent than requested. Treat these as severity-zero tenant-isolation incidents and do not retry: review docs/security/tenant-scoping.md before shipping the fix.
Retries, idempotency, and logging
- All mutating POSTs accept
idempotencyKey. The same key with the same body is safe to retry on transient failures; the same key with a different body returns409. - Log
tenant_id,intent_id,statusCode,url, correlation id (when present), and a redactedbodyText. Never log the API key, Harbor JWT, or capability token. - Backoff is exponential with jitter, bounded at ~5s per attempt;
Retry-Afteris honored up to 30s.