TypeScript SDK Reference
The official TypeScript SDK for Verity. Zero dependencies. Works in Node.js 18+ and any runtime with the Fetch API.
npm install @verityinc/sdkVerityClient
The main entry point. Create one instance and reuse it throughout your application.
import { VerityClient } from '@verityinc/sdk';
const verity = new VerityClient({
baseUrl: 'https://api.useverity.io/v1',
apiKey: process.env.VERITY_API_KEY!,
namespace: 'payments',
});Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
baseUrl | string | required | Verity API base URL |
apiKey | string | required | API key (vt_live_* or vt_test_*) |
namespace | string | undefined | Default namespace for all protect() calls |
agentId | string | undefined | Default agent/worker identifier attached to all requests |
autoRenew | boolean | true | Automatically renew leases in the background while act() runs |
renewAtFraction | number | 0.65 | Fraction of lease duration at which renewal fires (0.3–0.9) |
requestTimeoutMs | number | 20000 | Timeout for individual HTTP requests in ms |
conflictRetry | ConflictRetryConfig | See below | Retry behavior when another agent holds the lease |
logger | VerityLogger | console | Custom logger with warn, error, and optional debug/info |
ConflictRetryConfig
Controls automatic retry when another agent holds the lease (HTTP 409):
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable automatic retry on 409 |
maxAttempts | number | 12 | Maximum retry attempts |
initialDelayMs | number | 500 | Initial delay between retries in ms |
maxDelayMs | number | 15000 | Maximum delay between retries in ms |
jitter | boolean | true | Add ±30% random jitter to delays (prevents thundering herd) |
protect()
Protect a standalone effect. This is the primary API for single actions.
const result = await verity.protect<RefundResult>(effectKey, options, params?);Parameters
| Parameter | Type | Description |
|---|---|---|
effectKey | string | Unique key identifying this effect instance (your idempotency key) |
options | ProtectOptions<T> | observe (optional) + act (required) callbacks |
params | ProtectParams | Optional overrides (namespace, agentId, leaseDuration, etc.) |
ProtectOptions<T>
| Field | Type | Description |
|---|---|---|
observe | () => Promise<T | null | undefined> | Check external system for an already-completed action. Called only when priorState === 'expired'. |
act | () => Promise<T> | Execute the real-world side effect. Required. |
ProtectParams
| Field | Type | Default | Description |
|---|---|---|---|
namespace | string | Client default | Namespace override for this call |
effectName | string | undefined | Human-readable name (e.g., "stripe.charge") |
agentId | string | Client default | Agent identifier for this request |
leaseDurationMs | number | 30000 | Lease duration (5,000–120,000 ms) |
inputJson | unknown | undefined | Input data for debugging/replay (max 64 KB) |
onConflict | 'retry' | 'throw' | 'retry' | 'throw' — throw LeaseConflictError immediately on 409 |
onLease | (lease) => void | undefined | Callback after lease is acquired (for logging/metrics) |
keySuffix | string | undefined | Appended to effectKey for cardinality (e.g., recipient email) |
Return Value
Returns Promise<T> — the result from either act(), observe(), or the cached result from a prior commit.
Example
const refund = await verity.protect('refund:order_48392', {
observe: async () => {
const existing = await stripe.refunds.list({ charge: chargeId, limit: 1 });
return existing.data.length > 0 ? existing.data[0] : null;
},
act: async () => {
return await stripe.refunds.create({ charge: chargeId, amount: 4999 });
},
}, {
effectName: 'stripe.refund',
leaseDurationMs: 60_000, // 60s for slow Stripe calls
inputJson: { orderId, chargeId, amount: 4999 },
});Workflow Builder
For multi-effect processes, use the fluent workflow builder:
const run = verity.workflow(workflowName).case(caseId).run(options?);
await run.protect(effectName, options, overrides?);workflow(workflowName)
Returns a WorkflowContext bound to the workflow name. The workflow name is used as the default namespace and for grouping in the Explorer UI.
.case(caseId)
Returns a CaseContext bound to a specific case (business entity / customer intent). The case ID becomes part of the effect key: "${caseId}:${effectName}".
.run(options?)
Returns a RunContext — an execution attempt for the case. Options:
| Option | Type | Default | Description |
|---|---|---|---|
runId | string | Auto-generated | Explicit run ID for tracing |
namespace | string | workflowName | Namespace override |
leaseDurationMs | number | 30000 | Default lease duration for all effects in this run |
agentId | string | undefined | Agent identifier for all effects in this run |
run.protect(effectName, options, overrides?)
Protect an effect within the workflow run. The effect key is automatically derived as "${caseId}:${effectName}", ensuring idempotency across runs.
The overrides object accepts the same fields as ProtectParams plus:
namespace— per-effect namespace override (for cross-namespace workflows)keySuffix— appended for cardinality:"${caseId}:${effectName}:${keySuffix}"
Example
const run = verity.workflow('refund_flow').case('order_123').run();
await run.protect('validate_order', {
act: () => orderService.validate(orderId),
});
await run.protect('process_refund', {
observe: () => checkExistingRefund(chargeId),
act: () => stripe.refunds.create({ charge: chargeId }),
});
// Send to multiple recipients in the same workflow
for (const recipient of recipients) {
await run.protect('notify', {
act: () => emailService.send({ to: recipient }),
}, { keySuffix: recipient }); // effectKey = "order_123:notify:alice@example.com"
}Low-Level API
For power users who need manual control over the lease lifecycle. These methods are public on VerityClient:
requestLease(namespace, body)
const lease = await verity.requestLease('payments', {
effectKey: 'refund:order_123',
effectName: 'stripe.refund',
agentId: 'worker-1',
leaseDurationMs: 30_000,
});commit(namespace, body)
await verity.commit('payments', {
effectKey: 'refund:order_123',
fenceToken: lease.fenceToken,
leaseToken: lease.leaseToken,
result: { refundId: 're_123' },
source: 'acted',
});fail(namespace, body)
await verity.fail('payments', {
effectKey: 'refund:order_123',
fenceToken: lease.fenceToken,
leaseToken: lease.leaseToken,
error: { message: 'Stripe declined' },
source: 'acted',
});renew(namespace, body)
await verity.renew('payments', {
effectKey: 'refund:order_123',
fenceToken: lease.fenceToken,
leaseToken: lease.leaseToken,
extensionMs: 30_000,
});Error Classes
All errors extend VerityError. See Error Handling for complete details, including when each error is thrown and how to handle them.
| Error | When |
|---|---|
VerityApiError | Non-2xx response from Verity API |
LeaseConflictError | Another agent holds the lease (409, after retries exhausted) |
EffectPreviouslyFailedError | Effect already failed — admin reset required |
CommitUncertainError | Action succeeded but commit couldn't be confirmed — critical |
VerityConfigError | Missing required configuration (baseUrl, apiKey, namespace) |
VerityValidationError | Input data not JSON-serializable or exceeds 64 KB |
Exports
// Classes
import {
VerityClient,
WorkflowContext,
CaseContext,
RunContext,
} from '@verityinc/sdk';
// Errors
import {
VerityError,
VerityApiError,
VerityConfigError,
VerityValidationError,
LeaseConflictError,
EffectPreviouslyFailedError,
CommitUncertainError,
} from '@verityinc/sdk';
// Types
import type {
VerityConfig,
ConflictRetryConfig,
VerityLogger,
ProtectOptions,
ProtectParams,
WorkflowRunOptions,
LeaseResponse,
LeaseGrantedResponse,
LeaseCachedResponse,
CommitResponse,
FailResponse,
RenewResponse,
LeaseRequestBody,
CommitRequestBody,
FailRequestBody,
RenewRequestBody,
} from '@verityinc/sdk';