Bag is live โ€” accept USDC & card payments globally. Get started โ†’
BagBag Docs
SDKs

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

Works 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

OptionTypeRequiredDefaultDescription
apiKeystringYesโ€”Your Bag API key (bag_test_sk_* or bag_live_sk_*)
baseUrlstringNohttps://getbags.appAPI base URL (override for self-hosted or local dev)
timeoutnumberNo30000Request timeout in milliseconds
maxRetriesnumberNo2Max automatic retries on 5xx / network errors (0 to disable)

const link = await bag.paymentLinks.create({
  name: "Pro Plan",
  amount: 29.99,
  network: "base_sepolia",
});

console.log(link.id);     // "a1b2c3d4-..."
console.log(link.active); // true

Parameters:

FieldTypeRequiredDescription
namestringYesDisplay name
amountnumberYesPrice in USD
networkNetworkYesBlockchain network
descriptionstringNoDescription shown on checkout
currencystringNoDefaults to USD
tokenstringNoDefaults to USDC
networksNetwork[]NoMultiple chains
merchantWalletAddressstringNoOverride receiving wallet
merchantWalletAddressesRecord<string, string>NoPer-network wallets
targetUrlstringNoPost-payment redirect URL (HTTPS only)
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);
const link = await bag.paymentLinks.get("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
const updated = await bag.paymentLinks.update("a1b2c3d4-e5f6-7890-abcd-ef1234567890", {
  amount: 39.99,
  active: false,
});
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:

FieldTypeRequiredDescription
amountnumberYesPayment amount
tokenstringYesToken (e.g. USDC)
networkNetworkYesBlockchain network
txHashstringYesOn-chain transaction hash
walletAddressstringYesCustomer's wallet address
customerEmailstringNoCustomer email
customerNamestringNoCustomer name
customerAddressstringNoCustomer address
paymentLinkIdstringNoAssociated 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;
}
ParameterTypeDefaultMaxDescription
limitnumber25100Items per page
starting_afterstringโ€”โ€”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}`);
  }
}
PropertyTypeDescription
statusCodenumberHTTP status code (0 for timeouts/network errors)
messagestringError message
codestring | undefinedMachine-readable error code
requestIdstring | undefinedServer 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;
}

What's next

On this page