@paygentic/wrap
v0.1.2
Published
assure payment for agentic workflows
Downloads
282
Readme
Wrap SDK
Wrap Claude agent queries with automatic payment tracking and usage metering.
Paygentic Platform: Production | Sandbox
Quick Start
import { createWrapClient } from "@paygentic/wrap";
const client = createWrapClient();
const plan = await client.plans.init();
const query = plan.wrap("customer_123");
// wrapped query supports all claude agent sdk options
const response = await query({
model: "claude-sonnet-4-20250514",
prompt: "Hello, world!",
});
for await (const message of response) {
// Process messages as usual
// Usage is automatically tracked on completion
}Installation
npm install @paygentic/wrapCLI Setup
The package includes an interactive setup command that creates the necessary Paygentic resources (product, billable metric, price, and plan) for your agent:
npx @paygentic/wrap setupWhat It Does
The setup wizard will:
Prompt for configuration:
- Environment (
sandboxorprod) - API Key (from Paygentic Platform)
- Merchant ID (from Organization Settings)
- Agent name (used for product naming)
- Payment type (
instantorpost-paid) - Max expected usage per query (USD)
- Margin percentage (markup on usage costs)
- Environment (
Create Paygentic resources:
- Product for your agent
- Billable metric for tracking usage
- Price with your configured margin
- Plan linking everything together
Output environment variables to add to your
.envfile
Example Session
=== Paygentic Agent Billing Setup ===
Environment (sandbox/prod) [sandbox]: sandbox
Get your API key from: https://platform.sandbox.paygentic.io/merchant/developer/apikeys
API Key: pk_sandbox_xxxxx
Merchant ID: mer_xxxxx
Agent Name: My AI Assistant
Payment Type (instant/post-paid): instant
Max expected usage per query (USD) [2.00]: 2.00
Margin percentage [10]: 15
--- Creating Paygentic resources ---
Creating product...
Creating billable metric...
Creating price...
Creating plan...
=== Setup complete! ===
Add these to your .env file:
PAYGENTIC_WRAP_ENV=sandbox
PAYGENTIC_WRAP_API_KEY=pk_sandbox_xxxxx
PAYGENTIC_WRAP_MERCHANT_ID=mer_xxxxx
PAYGENTIC_WRAP_PRODUCT_ID=prod_xxxxx
PAYGENTIC_WRAP_PLAN_ID=plan_xxxxx
PAYGENTIC_WRAP_PRICE_ID=price_xxxxx
PAYGENTIC_WRAP_BILLABLE_METRIC_ID=bm_xxxxxEnvironment Variables
The SDK reads these environment variables as defaults:
| Variable | Description | Required |
|----------|-------------|----------|
| PAYGENTIC_WRAP_API_KEY | Your Paygentic API key | Yes (or pass apiKey option) |
| PAYGENTIC_WRAP_ENV | Environment: prod or sandbox | No (defaults to prod) |
| PAYGENTIC_WRAP_MERCHANT_ID | Your merchant ID | For setup script |
| PAYGENTIC_WRAP_PLAN_ID | Your plan ID | For your app |
| PAYGENTIC_WRAP_PRICE_ID | Your price ID | For your app |
| PAYGENTIC_WRAP_PRODUCT_ID | Your product ID | For reference |
| PAYGENTIC_WRAP_BILLABLE_METRIC_ID | Your billable metric ID | For reference |
Run npx @paygentic/wrap setup to create these resources and get the environment variables.
How It Works
When you call wrap, the SDK:
- Verifies the customer has an active subscription
- Sets a safe budget limit (90% of maxPrice or maxPrice - $0.10)
- Reserves funds for instant payments
- Tracks usage and creates billing/payment events on completion
- Handles overages by splitting into multiple events if needed
Pricing Models
Dynamic Pricing
Only pass through usage cost to end user:
// Price config: maxPrice >= $0.20
const plan = await client.plans.init({
planId: "plan_abc123",
priceId: "price_dynamic",
});Percentage Pricing
Apply a markup to usage cost:
// Price config: percentage, minCharge, maxCharge (maxCharge >= $0.20)
// Plan and price id default to PAYGENTIC_WRAP_PLAN_ID resp PAYGENTIC_WRAP_PRICE_ID
const plan = await client.plans.init({
planId: "plan_abc123",
priceId: "price_percentage",
});Subscriptions
Create a Customer and Subscription
const result = await client.subscriptions.create({
customer: {
name: "Example Inc",
email: "[email protected]",
address: {
line1: "123 Main St",
city: "San Francisco",
state: "CA",
postalCode: "94105",
country: "US",
},
},
minWalletAmount: "20.00",
redirectUrls: {
onSuccess: "https://example.com/success",
onFailure: "https://example.com/failure",
},
});
const { customerId, subscriptionDetail } = result;Get Customer Portal URL
const portal = await client.subscriptions.portal(subscriptionId);
// portal.url - redirect customers here to manage their subscription
// portal.expiresAt - URL expiration timeFind Customer by Email
const customerId = await client.subscriptions.findCustomer("[email protected]");Payment Terms
- Pre-paid (default): Creates an entitlement before the query runs
- In-arrears: Records usage after the query completes
Error Handling
Insufficient Funds
When a customer doesn't have enough balance to process a query, the SDK throws an InsufficientFundsError with a portal URL so they can top up:
import { createWrapClient, InsufficientFundsError } from "@paygentic/wrap";
const client = createWrapClient();
const plan = await client.plans.init();
const query = plan.wrap("customer_123");
try {
const response = await query({ model: "claude-sonnet-4-20250514", prompt: "Hello" });
for await (const message of response) {
// Process messages
}
} catch (error) {
if (error instanceof InsufficientFundsError) {
console.log("Please top up your balance:", error.portalUrl);
console.log("Link expires at:", error.portalExpiresAt);
console.log("Subscription ID:", error.subscriptionId);
// Redirect customer to error.portalUrl to add funds
} else {
throw error;
}
}The InsufficientFundsError includes:
portalUrl- URL where the customer can add funds to their walletportalExpiresAt- When the portal URL expires (ISO timestamp)subscriptionId- The subscription that needs funding
Query Stopped Due to Budget Limit
The SDK automatically sets a budget limit based on your pricing config to protect against runaway costs. When a query exceeds this limit, the agent stops and the result message has subtype: "error_max_budget_usd".
You can resume the query by passing the session_id in the options.resume field:
let sessionId: string | undefined;
const response = await query({ model: "claude-sonnet-4-20250514", prompt: "Complex task" });
for await (const message of response) {
if (message.type === "result") {
sessionId = message.session_id;
if (message.subtype === "error_max_budget_usd") {
console.log("Query stopped due to budget limit");
console.log("Cost incurred:", message.total_cost_usd);
}
}
}
// Resume the query if it stopped due to budget limit
if (sessionId) {
const continuation = await query({
model: "claude-sonnet-4-20250514",
prompt: "Please continue",
options: { resume: sessionId },
});
for await (const message of continuation) {
// Billing is handled automatically for the continuation
}
}