Verity
/Docs

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/sdk

VerityClient

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

OptionTypeDefaultDescription
baseUrlstringrequiredVerity API base URL
apiKeystringrequiredAPI key (vt_live_* or vt_test_*)
namespacestringundefinedDefault namespace for all protect() calls
agentIdstringundefinedDefault agent/worker identifier attached to all requests
autoRenewbooleantrueAutomatically renew leases in the background while act() runs
renewAtFractionnumber0.65Fraction of lease duration at which renewal fires (0.3–0.9)
requestTimeoutMsnumber20000Timeout for individual HTTP requests in ms
conflictRetryConflictRetryConfigSee belowRetry behavior when another agent holds the lease
loggerVerityLoggerconsoleCustom logger with warn, error, and optional debug/info

ConflictRetryConfig

Controls automatic retry when another agent holds the lease (HTTP 409):

OptionTypeDefaultDescription
enabledbooleantrueEnable automatic retry on 409
maxAttemptsnumber12Maximum retry attempts
initialDelayMsnumber500Initial delay between retries in ms
maxDelayMsnumber15000Maximum delay between retries in ms
jitterbooleantrueAdd ±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

ParameterTypeDescription
effectKeystringUnique key identifying this effect instance (your idempotency key)
optionsProtectOptions<T>observe (optional) + act (required) callbacks
paramsProtectParamsOptional overrides (namespace, agentId, leaseDuration, etc.)

ProtectOptions<T>

FieldTypeDescription
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

FieldTypeDefaultDescription
namespacestringClient defaultNamespace override for this call
effectNamestringundefinedHuman-readable name (e.g., "stripe.charge")
agentIdstringClient defaultAgent identifier for this request
leaseDurationMsnumber30000Lease duration (5,000–120,000 ms)
inputJsonunknownundefinedInput data for debugging/replay (max 64 KB)
onConflict'retry' | 'throw''retry''throw' — throw LeaseConflictError immediately on 409
onLease(lease) => voidundefinedCallback after lease is acquired (for logging/metrics)
keySuffixstringundefinedAppended 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:

OptionTypeDefaultDescription
runIdstringAuto-generatedExplicit run ID for tracing
namespacestringworkflowNameNamespace override
leaseDurationMsnumber30000Default lease duration for all effects in this run
agentIdstringundefinedAgent 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.

ErrorWhen
VerityApiErrorNon-2xx response from Verity API
LeaseConflictErrorAnother agent holds the lease (409, after retries exhausted)
EffectPreviouslyFailedErrorEffect already failed — admin reset required
CommitUncertainErrorAction succeeded but commit couldn't be confirmed — critical
VerityConfigErrorMissing required configuration (baseUrl, apiKey, namespace)
VerityValidationErrorInput 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';