Quickstart
Protect your first real-world action in under 5 minutes. This guide covers installation, configuration, and your first protect() call — in both TypeScript and Python.
Prerequisites
- A Verity account with an API key (starts with
vt_live_orvt_test_) - Node.js 18+ (TypeScript) or Python 3.10+ (Python)
vt_test_ keys write to an isolated test environment. Use them during development. vt_live_ keys write to production. Data between environments is completely separated via row-level security.1. Install the SDK
TypeScript
npm install @verityinc/sdkOr with your preferred package manager:
pnpm add @verityinc/sdk
yarn add @verityinc/sdkPython
pip install verityinc-sdk2. Initialize the Client
TypeScript
import { VerityClient } from '@verityinc/sdk';
const verity = new VerityClient({
baseUrl: 'https://api.useverity.io/v1',
apiKey: process.env.VERITY_API_KEY!, // vt_live_xxxxxxxx or vt_test_xxxxxxxx
namespace: 'payments', // logical grouping for your effects
});Python
from verity import VerityClient
verity = VerityClient(
base_url="https://api.useverity.io/v1",
api_key=os.environ["VERITY_API_KEY"], # vt_live_xxxxxxxx or vt_test_xxxxxxxx
namespace="payments", # logical grouping for your effects
)3. Protect Your First Action
Wrap any external side effect in verity.protect(). The first argument is a unique effect key — a string that identifies this specific action instance.
TypeScript
// Protect a Stripe refund — safe to retry, exactly once
const refund = await verity.protect('refund:order_48392', {
observe: async () => {
// Check if refund already happened (crash recovery)
const existing = await stripe.refunds.list({ charge: chargeId, limit: 1 });
return existing.data.length > 0 ? existing.data[0] : null;
},
act: async () => {
// Execute the actual refund
return await stripe.refunds.create({
charge: chargeId,
amount: 4999,
reason: 'requested_by_customer',
});
},
});
console.log('Refund ID:', refund.id);Python
# Protect a Stripe refund — safe to retry, exactly once
refund = await verity.protect(
"refund:order_48392",
observe=check_existing_refund, # Check if refund already happened
act=execute_refund, # Execute the actual refund
)
print(f"Refund ID: {refund['id']}")What Just Happened?
Behind a single protect() call, Verity executed this protocol:
- Lease — acquired an exclusive lock on
refund:order_48392using a monotonically increasing fence token. No other agent can touch this effect. - Observe — since this is a fresh effect (
priorState: 'none'), the observe step was skipped. If a prior attempt had crashed, Verity would have called yourobserve()function to check Stripe before acting again. - Act — your
act()function ran, creating the refund in Stripe. - Commit — the refund result was recorded in Verity's ledger. Any future call with the same effect key returns the cached result instantly — no duplicate refund.
The entire flow is visible in the Explorer UI as an audit trail with timestamps, fence tokens, and agent identifiers.
Simpler: Without Observe
If your action is naturally idempotent (or you don't need crash recovery), you can skip the observe callback:
// TypeScript — just act()
const result = await verity.protect('send-welcome:user_42', {
act: () => emailService.send({ to: user.email, template: 'welcome' }),
});# Python — just act()
result = await verity.protect(
"send-welcome:user_42",
act=lambda: email_service.send(to=user.email, template="welcome"),
)Without observe, Verity still gives you exactly-once guarantees via leasing and fence tokens. If your agent retries, Verity returns the cached result from the first successful execution.
observe — use it when your action has real-world cost (money, infrastructure, notifications) and a prior attempt might have succeeded before crashing. The observe function lets you check the external system before acting again.Protect Multiple Actions (Workflows)
For multi-step processes, use the workflow builder to group effects under a case:
// TypeScript — multi-effect workflow
const run = verity.workflow('refund_flow').case('order_123').run();
// Each effect gets a deterministic key: "order_123:validate_order"
await run.protect('validate_order', {
act: () => orderService.validate(orderId),
});
await run.protect('process_refund', {
observe: () => checkExistingRefund(chargeId),
act: () => stripe.refunds.create({ charge: chargeId }),
});
await run.protect('notify_customer', {
act: () => emailService.send({ to: email, template: 'refund_complete' }),
});Each effect in the workflow is independently protected. If the agent crashes after the refund but before the email, a re-run will skip the completed refund (cached) and only send the email. See Workflows for full details.
Next Steps
- Core Concepts — understand effects, leases, fence tokens, and the observe/act pattern in depth
- TypeScript SDK Reference — full API documentation
- Python SDK Reference — full API documentation
- Error Handling — understand
CommitUncertainError,LeaseConflictError, and more - Explorer UI — observe effects and trace failures visually