TypeScript SDK
Install, configure, and use the @tbagtapp/sdk package to integrate Bag.
TypeScript SDK
The official Bag SDK for TypeScript and JavaScript. It wraps the Bag REST API with typed methods, automatic retries, pagination, and a clean interface.
npm install @tbagtapp/sdkWorks with ESM and CommonJS โ use import or require.
Setup
import { Bag } from "@tbagtapp/sdk";
const bag = new Bag({
apiKey: process.env.BAG_API_KEY!,
});Configuration
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey | string | Yes | โ | Your Bag API key (bag_test_sk_* or bag_live_sk_*) |
baseUrl | string | No | https://getbags.app | API base URL (override for self-hosted or local dev) |
timeout | number | No | 30000 | Request timeout in milliseconds |
maxRetries | number | No | 2 | Max automatic retries on 5xx / network errors (0 to disable) |
Payment Links
Create a payment link
const link = await bag.paymentLinks.create({
name: "Pro Plan",
amount: 29.99,
network: "base_sepolia",
});
console.log(link.id); // "a1b2c3d4-..."
console.log(link.active); // trueParameters:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name |
amount | number | Yes | Price in USD |
network | Network | Yes | Blockchain network |
description | string | No | Description shown on checkout |
currency | string | No | Defaults to USD |
token | string | No | Defaults to USDC |
networks | Network[] | No | Multiple chains |
merchantWalletAddress | string | No | Override receiving wallet |
merchantWalletAddresses | Record<string, string> | No | Per-network wallets |
targetUrl | string | No | Post-payment redirect URL (HTTPS only) |
List payment links
const { data, hasMore } = await bag.paymentLinks.list({ limit: 10 });
// Paginate through all links
let cursor: string | undefined;
do {
const page = await bag.paymentLinks.list({
limit: 25,
starting_after: cursor,
});
for (const link of page.data) {
console.log(link.name, link.amount);
}
cursor = page.hasMore ? page.data[page.data.length - 1].id : undefined;
} while (cursor);Get a payment link
const link = await bag.paymentLinks.get("a1b2c3d4-e5f6-7890-abcd-ef1234567890");Update a payment link
const updated = await bag.paymentLinks.update("a1b2c3d4-e5f6-7890-abcd-ef1234567890", {
amount: 39.99,
active: false,
});Delete a payment link
await bag.paymentLinks.delete("a1b2c3d4-e5f6-7890-abcd-ef1234567890");Transactions
List transactions
const { data, hasMore } = await bag.transactions.list({ limit: 50 });Create a transaction
const tx = await bag.transactions.create({
amount: 29.99,
token: "USDC",
network: "base_sepolia",
txHash: "0xabc123...",
walletAddress: "0xCustomerWallet",
customerEmail: "customer@example.com",
paymentLinkId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
});Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Payment amount |
token | string | Yes | Token (e.g. USDC) |
network | Network | Yes | Blockchain network |
txHash | string | Yes | On-chain transaction hash |
walletAddress | string | Yes | Customer's wallet address |
customerEmail | string | No | Customer email |
customerName | string | No | Customer name |
customerAddress | string | No | Customer address |
paymentLinkId | string | No | Associated payment link |
Checkout
Get a tax quote
const quote = await bag.checkout.getTaxQuote({
paymentLinkId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
customerAddress: {
address_line_1: "100 Main St",
address_city: "San Francisco",
address_province: "CA",
address_postal_code: "94105",
address_country: "US",
address_type: "billing",
},
});Returns: { subtotalCents, taxCents, totalCents, calculationId, quoteToken }
Create a checkout session
const session = await bag.checkout.createSession({
linkId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
quoteToken: quote.quoteToken,
walletAddress: "0xCustomerWallet",
walletType: "evm",
network: "base_sepolia",
customer: {
name: "Jane Doe",
email: "jane@example.com",
address: "100 Main St, San Francisco, CA 94105",
country: "US",
},
totalsSnapshot: {
subtotalCents: quote.subtotalCents,
taxCents: quote.taxCents,
totalCents: quote.totalCents,
calculationId: quote.calculationId,
},
});Get a checkout session
const status = await bag.checkout.getSession("session-uuid");
// status.status is a typed union:
// "payment_session_created" | "txn_broadcast" | "txn_confirming" |
// "txn_finalized" | "complete" | "failed" | "expired" | "manual_retry_needed"Submit a transaction hash
await bag.checkout.submit("session-uuid", "0xabc123...");Automatic retries
The SDK automatically retries requests that fail with 500, 502, 503, 504, 408, 429, or network errors. Retries use exponential backoff with jitter.
const bag = new Bag({
apiKey: process.env.BAG_API_KEY!,
maxRetries: 3, // up to 4 total attempts (default: 2)
});
// Disable retries entirely
const bag2 = new Bag({
apiKey: process.env.BAG_API_KEY!,
maxRetries: 0,
});Non-retryable errors (400, 401, 403, 404, 422) throw immediately.
Timeouts
Every request has a timeout. The default is 30 seconds.
// Global timeout
const bag = new Bag({
apiKey: process.env.BAG_API_KEY!,
timeout: 10_000, // 10 seconds
});
// Per-request override
const link = await bag.paymentLinks.get("link-id", { timeout: 5000 });
// Manual abort
const controller = new AbortController();
setTimeout(() => controller.abort(), 3000);
const links = await bag.paymentLinks.list({}, { signal: controller.signal });Idempotency keys
Pass an idempotency key on any mutating request to prevent duplicate operations:
const link = await bag.paymentLinks.create(
{ name: "Pro Plan", amount: 29.99, network: "base" },
{ idempotencyKey: "create-pro-plan-user-123" },
);If you retry the same request with the same key, the server returns the original response instead of creating a duplicate.
Pagination
List methods return { data, hasMore }. Use cursor-based pagination with starting_after:
import type { PaymentLink, PaginatedList } from "@tbagtapp/sdk";
async function getAllLinks(bag: Bag): Promise<PaymentLink[]> {
const all: PaymentLink[] = [];
let cursor: string | undefined;
do {
const page: PaginatedList<PaymentLink> = await bag.paymentLinks.list({
limit: 100,
starting_after: cursor,
});
all.push(...page.data);
cursor = page.hasMore ? page.data[page.data.length - 1].id : undefined;
} while (cursor);
return all;
}| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
limit | number | 25 | 100 | Items per page |
starting_after | string | โ | โ | ID of the last item from the previous page |
Error handling
The SDK throws BagError for API errors:
import { Bag, BagError } from "@tbagtapp/sdk";
try {
await bag.paymentLinks.create({ name: "Test", amount: 0, network: "base" });
} catch (error) {
if (error instanceof BagError) {
console.error(`${error.statusCode}: ${error.message}`);
console.error(`Code: ${error.code}`);
console.error(`Request ID: ${error.requestId}`);
}
}| Property | Type | Description |
|---|---|---|
statusCode | number | HTTP status code (0 for timeouts/network errors) |
message | string | Error message |
code | string | undefined | Machine-readable error code |
requestId | string | undefined | Server request ID for debugging |
Types
Network
type Network =
| "base" | "ethereum" | "polygon" | "solana"
| "base_sepolia" | "eth_sepolia" | "solana_devnet";TransactionStatus
type TransactionStatus =
| "broadcasted" | "pending" | "confirming"
| "completed" | "failed" | "refunded";CheckoutSessionState
type CheckoutSessionState =
| "payment_session_created" | "txn_broadcast" | "txn_confirming"
| "txn_finalized" | "complete" | "failed" | "expired" | "manual_retry_needed";PaginatedList
interface PaginatedList<T> {
data: T[];
hasMore: boolean;
}RequestOptions
interface RequestOptions {
idempotencyKey?: string;
timeout?: number;
signal?: AbortSignal;
}