JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 51
  • Score
    100M100P100Q63624F
  • License MIT

SDK for the AlignLab API - Build powerful payment infrastructure with fiat-to-crypto, crypto-to-fiat, and cross-chain transfers

Package Exports

  • @tolbel/align

Readme

AlignLab TypeScript SDK

Unofficial TypeScript/JavaScript SDK for the AlignLab API. Build powerful payment infrastructure with support for fiat-to-crypto (onramp), crypto-to-fiat (offramp), cross-chain transfers, virtual accounts, and more.

npm version TypeScript License: MIT

Features

  • 🔐 Type-Safe: Full TypeScript support with comprehensive type definitions
  • 🚀 Modern: Built with ES modules and async/await
  • Validated: Request validation with Zod schemas
  • 🔒 Secure: HMAC-SHA256 webhook signature verification
  • 📦 Lightweight: Minimal dependencies
  • 🌍 Environment Support: Sandbox and production environments
  • 🔄 Automatic Retry: Built-in retry mechanism with exponential backoff for transient errors
  • 📝 Logging: Optional request/response logging with pino (disabled by default)

Installation

npm install @tolbel/align
yarn add @tolbel/align
pnpm add @tolbel/align
bun add @tolbel/align

Quick Start

import Align from "@tolbel/align";

// Initialize the client
const align = new Align({
  apiKey: "your_api_key_here",
  environment: "sandbox", // or 'production'
});

// Create a customer
const customer = await align.customers.create({
  email: "user@example.com",
  first_name: "John",
  last_name: "Doe",
  type: "individual",
});

console.log("Customer created:", customer.customer_id);

Table of Contents


Configuration

AlignConfig

interface AlignConfig {
  /**
   * Your AlignLab API Key
   */
  apiKey: string;

  /**
   * Environment to use
   * @default 'sandbox'
   */
  environment?: "sandbox" | "production";

  /**
   * Custom base URL (useful for proxying)
   */
  baseUrl?: string;

  /**
   * Request timeout in milliseconds
   * @default 30000
   */
  timeout?: number;

  /**
   * Enable logging for debugging
   * @default false
   */
  debug?: boolean;

  /**
   * Log level threshold
   * @default 'error'
   */
  logLevel?: "error" | "warn" | "info" | "debug";
}

Example

const align = new Align({
  apiKey: process.env.ALIGNLAB_API_KEY!,
  environment: "production",
  timeout: 60000, // 60 seconds
});

Customers

Manage customer accounts for your platform.

Types

// Shared types
type CustomerType = "individual" | "corporate";
type KycStatus = "pending" | "approved" | "rejected" | "not_started";

interface Customer {
  customer_id: string;
  email: string;
  first_name?: string;
  last_name?: string;
  company_name?: string;
  type: CustomerType;
  kycs?: {
    status_breakdown: Array<{
      currency: string;
      payment_rails: string;
      status: KycStatus;
    }>;
    kyc_flow_link: string;
  };
}

interface CreateCustomerRequest {
  email: string;
  type: CustomerType;
  first_name?: string;
  last_name?: string;
  company_name?: string;
}

interface UpdateCustomerRequest {
  documents: Array<{
    file_id: string;
    purpose: string;
  }>;
}

interface KycSessionResponse {
  kycs: {
    kyc_flow_link: string;
  };
}

Create Customer

const customer = await align.customers.create({
  email: "alice@example.com",
  first_name: "Alice",
  last_name: "Smith",
  type: "individual",
});

console.log(customer.customer_id); // "123e4567-e89b-12d3-a456-426614174000"

Get Customer

const customer = await align.customers.get(
  "123e4567-e89b-12d3-a456-426614174000"
);

console.log(customer.email); // "alice@example.com"
console.log(customer.kycs?.status_breakdown[0].status); // "approved"

Update Customer

const updatedCustomer = await align.customers.update("cus_abc123", {
  email: "alice.smith@example.com",
  first_name: "Alice Marie",
});

console.log(updatedCustomer.email); // "alice.smith@example.com"

List Customers

// List all customers (use with caution - no pagination support)
const customers = await align.customers.list();

console.log(customers.items.length); // Number of customers returned

// Filter by email (recommended for finding specific customer)
const filtered = await align.customers.list("alice@example.com");
console.log(filtered.items[0]?.customer_id);

Create KYC Session

const kycSession = await align.customers.createKycSession("cus_abc123");

console.log(kycSession.url); // "https://kyc.alignlabs.dev/session/..."
console.log(kycSession.session_id); // "kyc_session_xyz"

// Redirect user to kycSession.url to complete KYC

Virtual Accounts

Create virtual bank accounts for customers to receive payments.

Types

interface VirtualAccount {
  id: string;
  status: "active";
  destination_token: "usdc" | "usdt" | "aed";
  destination_network:
    | "polygon"
    | "ethereum"
    | "solana"
    | "base"
    | "tron"
    | "arbitrum";
  destination_address: string;
  deposit_instructions: {
    bank_name: string;
    bank_address: string;
    account_holder_name: string;
    // ... other bank details (IBAN or US)
  };
}

interface CreateVirtualAccountRequest {
  source_currency: "usd" | "eur" | "aed";
  destination_token: "usdc" | "usdt";
  destination_network:
    | "polygon"
    | "ethereum"
    | "solana"
    | "base"
    | "tron"
    | "arbitrum";
  destination_address: string;
}

Create Virtual Account

const virtualAccount = await align.virtualAccounts.create(customerId, {
  source_currency: "eur",
  destination_token: "usdc",
  destination_network: "polygon",
  destination_address: "0x742d35...",
});

console.log(virtualAccount.id);
console.log(virtualAccount.deposit_instructions.bank_name);

List Virtual Accounts

const accounts = await align.virtualAccounts.list(customerId);

accounts.items.forEach((account) => {
  console.log(
    `${account.destination_token.toUpperCase()}: ${
      account.deposit_instructions.bank_name
    }`
  );
});

Get Virtual Account

const account = await align.virtualAccounts.get(customerId, "va_abc123");

console.log(account.status); // "active"

Transfers

Offramp (Crypto to Fiat)

Convert cryptocurrency to fiat currency.

Types

type PaymentRail = "ach" | "wire" | "sepa" | "swift" | "uaefts";
type FiatCurrency = "usd" | "eur" | "aed";
type CryptoToken = "usdc" | "usdt" | "eurc";
type BlockchainNetwork =
  | "polygon"
  | "ethereum"
  | "solana"
  | "base"
  | "arbitrum"
  | "tron";

interface CreateOfframpQuoteRequest {
  source_amount?: string;
  destination_amount?: string;
  source_token: CryptoToken;
  source_network: BlockchainNetwork;
  destination_currency: FiatCurrency;
  destination_payment_rails: PaymentRail;
  developer_fee_percent?: string;
}

interface QuoteResponse {
  quote_id: string;
  source_amount: string;
  source_token?: string;
  source_network?: string;
  destination_amount: string;
  destination_currency?: string;
  destination_payment_rails?: string;
  fee_amount: string;
  exchange_rate: string;
}

interface CreateTransferFromQuoteRequest {
  transfer_purpose: string;
  destination_external_account_id?: string;
  destination_bank_account_details?: Record<string, unknown>;
}

interface Transfer {
  id: string;
  amount: string;
  currency: string;
  status: "pending" | "completed" | "failed";
  created_at: string;
  updated_at: string;
}

Create Offramp Quote

// Quote with source amount (you know how much crypto to send)
const quote = await align.transfers.createOfframpQuote({
  source_amount: "100.00",
  source_token: "usdc",
  source_network: "polygon",
  destination_currency: "usd",
  destination_payment_rails: "ach",
  developer_fee_percent: "0.5", // Optional 0.5% fee
});

console.log(`Send ${quote.source_amount} USDC`);
console.log(`Receive ${quote.destination_amount} USD`);
console.log(`Exchange rate: ${quote.exchange_rate}`);
console.log(`Fee: ${quote.fee_amount}`);

// Quote with destination amount (you know how much fiat to receive)
const quote2 = await align.transfers.createOfframpQuote({
  destination_amount: "95.00",
  source_token: "usdc",
  source_network: "ethereum",
  destination_currency: "usd",
  destination_payment_rails: "wire",
});

console.log(`Send ${quote2.source_amount} USDC to receive $95 USD`);

Offramp Transfers (Crypto to Fiat)

  1. Create a Quote

    const quote = await align.transfers.createOfframpQuote(
      customer.customer_id,
      {
        source_amount: "100.00",
        source_token: "usdc",
        source_network: "polygon",
        destination_currency: "usd",
        destination_payment_rails: "ach",
      }
    );
  2. Create Transfer from Quote

    const transfer = await align.transfers.createOfframpTransfer(
      customer.customer_id,
      quote.quote_id,
      {
        transfer_purpose: "commercial_investment",
        // Option A: Use existing external account
        destination_external_account_id: "ext_acc_123",
    
        // Option B: Provide bank details directly
        /*
        destination_bank_account: {
          bank_name: 'Chase Bank',
          account_holder_type: 'individual',
          account_holder_first_name: 'John',
          account_holder_last_name: 'Doe',
          account_holder_address: {
            country: 'US',
            city: 'San Francisco',
            street_line_1: '123 Main St',
            postal_code: '94105'
          },
          account_type: 'us',
          us: {
            account_number: '1234567890',
            routing_number: '021000021'
          }
        }
        */
      }
    );
  3. Complete Transfer (After Deposit)

    const completedTransfer = await align.transfers.completeOfframpTransfer(
      customer.customer_id,
      transfer.id,
      {
        deposit_transaction_hash: "0x1234567890abcdef...",
      }
    );
  4. List Transfers

    const transfers = await align.transfers.listOfframpTransfers(
      customer.customer_id
    );
    console.log(transfers.items);

Get Offramp Transfer

const transfer = await align.transfers.getOfframpTransfer("transfer_abc123");

console.log(transfer.status); // "completed"
console.log(transfer.amount); // "95.00"

List Offramp Transfers

const transfers = await align.transfers.listOfframpTransfers();

transfers.forEach((transfer) => {
  console.log(`${transfer.id}: ${transfer.status} - $${transfer.amount}`);
});

Onramp (Fiat to Crypto)

Convert fiat currency to cryptocurrency.

Types

interface CreateOnrampQuoteRequest {
  source_amount?: string;
  destination_amount?: string;
  source_currency: FiatCurrency;
  source_payment_rails: PaymentRail;
  destination_token: CryptoToken;
  destination_network: BlockchainNetwork;
  developer_fee_percent?: string;
}

Create Onramp Quote

const quote = await align.transfers.createOnrampQuote(
  "123e4567-e89b-12d3-a456-426614174000",
  {
    source_amount: "100.00",
    source_currency: "usd",
    source_payment_rails: "ach",
    destination_token: "usdc",
    destination_network: "polygon",
  }
);

Create Onramp Transfer

const transfer = await align.transfers.createOnrampTransfer(
  "123e4567-e89b-12d3-a456-426614174000",
  quote.quote_id,
  {
    destination_address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
  }
);

Simulate Offramp Transfer (Sandbox)

const result = await align.transfers.simulateOfframpTransfer(
  "123e4567-e89b-12d3-a456-426614174000",
  {
    action: "complete_transfer",
    transfer_id: "transfer_abc123",
  }
);

Get Onramp Transfer

const transfer = await align.transfers.getOnrampTransfer(
  "123e4567-e89b-12d3-a456-426614174000",
  "transfer_xyz789"
);

List Onramp Transfers

const transfers = await align.transfers.listOnrampTransfers();

transfers.forEach((transfer) => {
  console.log(`${transfer.id}: ${transfer.status}`);
});

Simulate Transfer (Sandbox Only)

// Simulate transfer completion in sandbox
const simulatedTransfer = await align.transfers.simulate(
  "transfer_abc123",
  "completed"
);

console.log(simulatedTransfer.status); // "completed"

// Simulate transfer failure
const failedTransfer = await align.transfers.simulate(
  "transfer_xyz789",
  "failed"
);

console.log(failedTransfer.status); // "failed"

Cross-Chain Transfers

Transfer cryptocurrency across different blockchain networks.

Types

interface CreateCrossChainQuoteRequest {
  source_token: CryptoToken;
  source_network: BlockchainNetwork;
  destination_token: CryptoToken;
  destination_network: BlockchainNetwork;
  amount: string;
  is_source_amount: boolean;
}

interface CrossChainQuote {
  quote_id: string;
  source_amount: string;
  destination_amount: string;
  exchange_rate: string;
  fee: string;
  expires_at: string;
}

interface CreateCrossChainTransferRequest {
  quote_id: string;
  destination_address: string;
}

interface CrossChainTransfer {
  id: string;
  quote_id: string;
  status: "pending" | "completed" | "failed";
  source_amount: string;
  destination_amount: string;
  created_at: string;
}

interface PermanentRoute {
  id: string;
  source_token: CryptoToken;
  source_network: BlockchainNetwork;
  destination_token: CryptoToken;
  destination_network: BlockchainNetwork;
  deposit_address: string;
}

Create Cross-Chain Quote

const quote = await align.crossChain.createQuote({
  source_token: "usdc",
  source_network: "ethereum",
  destination_token: "usdc",
  destination_network: "polygon",
  amount: "100.00",
  is_source_amount: true,
});

console.log(`Quote ID: ${quote.quote_id}`);
console.log(`Send ${quote.source_amount} USDC on Ethereum`);
console.log(`Receive ${quote.destination_amount} USDC on Polygon`);
console.log(`Fee: ${quote.fee}`);
console.log(`Expires at: ${quote.expires_at}`);

Cross-Chain Transfers

Transfer cryptocurrency across different blockchain networks.

Create a Cross-Chain Transfer

const transfer = await align.crossChain.createTransfer(customerId, {
  amount: "100.00",
  source_network: "ethereum",
  source_token: "usdc",
  destination_network: "polygon",
  destination_token: "usdc",
  destination_address: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
});

console.log(`Transfer ID: ${transfer.id}`);
console.log(`Status: ${transfer.status}`);
console.log(
  `Fee: ${transfer.quote.fee_amount} ${transfer.quote.deposit_token}`
);

Complete a Cross-Chain Transfer

After sending the funds to the deposit address provided in the transfer response, you must complete the transfer by providing the transaction hash.

const completedTransfer = await align.crossChain.completeTransfer(
  customerId,
  transfer.id,
  {
    deposit_transaction_hash:
      "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
  }
);

Get Transfer Details

const transfer = await align.crossChain.getTransfer(
  customerId,
  "transfer_uuid"
);

Permanent Routes

Create a permanent deposit address for recurring transfers.

// Create a permanent route
const route = await align.crossChain.createPermanentRouteAddress(customerId, {
  destination_network: "polygon",
  destination_token: "usdc",
  destination_address: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
});

// List all routes
const routes = await align.crossChain.listPermanentRouteAddresses(customerId);

console.log(Deposit Address: ${route.deposit_address}); console.log(Route ID: ${route.id});

// Any USDC sent to this address on Ethereum will automatically // be bridged to Solana and sent to the destination address


### List Permanent Routes

```typescript
const routes = await align.crossChain.listPermanentRoutes();

routes.forEach(route => {
  console.log(`${route.source_network} → ${route.destination_network}`);
  console.log(`Deposit: ${route.deposit_address}`);
});

External Accounts

Link external bank accounts for fiat transfers.

Types

interface Address {
  street: string;
  city: string;
  state: string;
  postal_code: string;
  country: string;
}

interface IbanDetails {
  iban: string;
  bic?: string;
}

interface UsDetails {
  account_number: string;
  routing_number: string;
  account_type: "checking" | "savings";
}

interface CreateExternalAccountRequest {
  account_holder_name: string;
  account_holder_type: "individual" | "business";
  currency: FiatCurrency;
  country: string;
  address: Address;
  iban_details?: IbanDetails;
  us_details?: UsDetails;
}

interface ExternalAccount {
  id: string;
  account_holder_name: string;
  account_holder_type: "individual" | "business";
  currency: FiatCurrency;
  country: string;
  status: "pending" | "verified" | "failed";
  created_at: string;
}

Create External Account (US)

const account = await align.externalAccounts.create({
  account_holder_name: "John Doe",
  account_holder_type: "individual",
  currency: "usd",
  country: "US",
  address: {
    street: "123 Main St",
    city: "New York",
    state: "NY",
    postal_code: "10001",
    country: "US",
  },
  us_details: {
    account_number: "1234567890",
    routing_number: "021000021",
    account_type: "checking",
  },
});

console.log(account.id); // "ext_acc_123"
console.log(account.status); // "pending"

Create External Account (IBAN)

const account = await align.externalAccounts.create({
  account_holder_name: "Jane Smith",
  account_holder_type: "individual",
  currency: "eur",
  country: "DE",
  address: {
    street: "Hauptstraße 1",
    city: "Berlin",
    state: "Berlin",
    postal_code: "10115",
    country: "DE",
  },
  iban_details: {
    iban: "DE89370400440532013000",
    bic: "COBADEFFXXX",
  },
});

console.log(account.id); // "ext_acc_456"

Get External Account

const account = await align.externalAccounts.get("ext_acc_123");

console.log(account.status); // "verified"
console.log(account.currency); // "usd"

Wallets

Verify wallet ownership for cryptocurrency addresses.

Types

interface VerifyWalletRequest {
  wallet_address: string;
}

interface WalletVerification {
  verification_flow_link: string;
}

Verify Wallet Ownership

const verification = await align.wallets.verifyOwnership(customerId, {
  wallet_address: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
});

console.log(verification.verification_flow_link);
// "https://verify.alignlabs.dev/wallet/..."

console.log(verification.status); // "pending"

// User clicks the link and signs a message with their wallet
// Status will change to "verified"

Webhooks

Manage webhook endpoints and verify webhook signatures.

Types

// Shared type
type WebhookStatus = "active" | "inactive";

type WebhookEventType =
  | "customer.kycs.updated"
  | "onramp_transfer.status.updated"
  | "offramp_transfer.status.updated";

type WebhookEntityType = "customer" | "onramp_transfer" | "offramp_transfer";

interface Webhook {
  id: string;
  url: string;
  status: WebhookStatus;
  created_at: string;
}

interface CreateWebhookRequest {
  url: string;
}

interface WebhookListResponse {
  items: Webhook[];
}

// This is the payload you receive when a webhook is triggered
interface WebhookEvent {
  event_type: WebhookEventType;
  entity_id: string;
  entity_type: WebhookEntityType;
  created_at: string;
}

Create Webhook

const webhook = await align.webhooks.create({
  url: "https://your-domain.com/webhooks/alignlab",
});

console.log(webhook.id); // "wh_abc123"
console.log(webhook.status); // "active"

List Webhooks

const response = await align.webhooks.list();

response.items.forEach((webhook) => {
  console.log(`${webhook.id}: ${webhook.url}`);
});

Delete Webhook

await align.webhooks.delete("wh_abc123");

console.log("Webhook deleted");

Verify Webhook Signature

Verify that webhook requests are genuinely from AlignLab using HMAC-SHA256 signature verification.

[!IMPORTANT] The webhook signature is sent in the x-hmac-signature header.

import express from "express";
import type { WebhookEvent } from "@tolbel/align";

const app = express();

app.post(
  "/webhooks/alignlab",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["x-hmac-signature"] as string;
    const payload = req.body.toString("utf8");
    const apiKey = process.env.ALIGNLAB_API_KEY!; // Use your API key as the secret

    // Verify the signature
    const isValid = align.webhooks.verifySignature(payload, signature, apiKey);

    if (!isValid) {
      console.error("Invalid webhook signature");
      return res.status(401).send("Invalid signature");
    }

    // Process the webhook event
    const event: WebhookEvent = JSON.parse(payload);
    console.log("Webhook event:", event.event_type);

    switch (event.event_type) {
      case "customer.kycs.updated":
        console.log("Customer KYC updated:", event.entity_id);
        break;
      case "onramp_transfer.status.updated":
        console.log("Onramp transfer status updated:", event.entity_id);
        break;
      case "offramp_transfer.status.updated":
        console.log("Offramp transfer status updated:", event.entity_id);
        break;
    }

    res.status(200).send("OK");
  }
);

Files

Upload files for KYC verification and compliance.

Upload File

import fs from "fs";

const fileBuffer = fs.readFileSync("./passport.pdf");
const formData = new FormData();
formData.append("file", new Blob([fileBuffer]), "passport.pdf");
formData.append("purpose", "kyc_document");

const file = await align.files.upload(fileInput.files[0]);

console.log(file.id); // "file_abc123"
console.log(file.name); // "passport.pdf"
console.log(file.type); // "application/pdf"

Developers

Manage developer fees for your platform.

Types

interface DeveloperFeesResponse {
  developer_receivable_fees: Array<{
    service_type: "onramp" | "offramp" | "cross_chain_transfer";
    accrual_basis: "percentage";
    value: number;
  }>;
}

Get Developer Fees

const response = await align.developers.getFees();

response.developer_receivable_fees.forEach((fee) => {
  console.log(`${fee.service_type}: ${fee.value}% (${fee.accrual_basis})`);
});

Update Developer Fees

const response = await align.developers.updateFees({
  developer_receivable_fees: {
    onramp: 1,
    offramp: 1,
    cross_chain_transfer: 1,
  },
});

console.log("Fees updated successfully");

---

## Error Handling

The SDK provides custom error classes for better error handling.

### Error Types

```typescript
import { AlignError, AlignValidationError } from '@tolbel/align';

try {
  const customer = await align.customers.create({
    email: 'invalid-email', // Invalid email format
    first_name: 'John',
    last_name: 'Doe',
    type: 'individual',
  });
} catch (error) {
  if (error instanceof AlignValidationError) {
    console.error('Validation error:', error.message);
    console.error('Field errors:', error.fieldErrors);
    // Field errors: { email: ['Invalid email'] }
  } else if (error instanceof AlignError) {
    console.error('API error:', error.message);
    console.error('Status code:', error.statusCode);
  } else {
    console.error('Unexpected error:', error);
  }
}

Handling API Errors

try {
  const transfer = await align.transfers.createOfframpTransfer({
    transfer_purpose: "Payment",
    destination_external_account_id: "invalid_id",
  });
} catch (error) {
  if (error instanceof AlignError) {
    switch (error.statusCode) {
      case 400:
        console.error("Bad request:", error.message);
        break;
      case 401:
        console.error("Unauthorized - check your API key");
        break;
      case 404:
        console.error("Resource not found");
        break;
      case 429:
        console.error("Rate limit exceeded");
        break;
      case 500:
        console.error("Server error");
        break;
      default:
        console.error("API error:", error.message);
    }
  }
}

TypeScript Types

The SDK is fully typed. Import types as needed:

import type {
  // Core
  AlignConfig,
  AlignEnvironment,

  // Shared Types (NEW in v1.0.2)
  KycStatus,
  WebhookStatus,
  CustomerType,

  // Customers
  Customer,
  CreateCustomerRequest,
  UpdateCustomerRequest,
  KycSessionResponse,

  // Virtual Accounts
  VirtualAccount,
  CreateVirtualAccountRequest,

  // Transfers
  Transfer,
  QuoteResponse,
  CreateOfframpQuoteRequest,
  CreateOnrampQuoteRequest,
  CreateTransferFromQuoteRequest,
  PaymentRail,
  FiatCurrency,
  CryptoToken,
  BlockchainNetwork,

  // Cross-Chain
  CrossChainQuote,
  CrossChainTransfer,
  CreateCrossChainQuoteRequest,
  CreateCrossChainTransferRequest,
  PermanentRoute,

  // External Accounts
  ExternalAccount,
  CreateExternalAccountRequest,
  Address,
  IbanDetails,
  UsDetails,

  // Wallets
  VerifyWalletRequest,
  WalletVerification,

  // Webhooks
  Webhook,
  CreateWebhookRequest,
  WebhookEvent,
  WebhookEventType,
  WebhookEntityType,
  WebhookListResponse,

  // Developers
  DeveloperFee,

  // Errors
  AlignError,
  AlignValidationError,
} from "@tolbel/align";

Advanced Usage

Custom HTTP Client Configuration

import Align from "@tolbel/align";

const align = new Align({
  apiKey: process.env.ALIGNLAB_API_KEY!,
  environment: "production",
  timeout: 60000, // 60 seconds
  baseUrl: "https://your-proxy.com/alignlab", // Custom proxy
});

Debug Logging

Enable logging to debug requests and responses:

import Align from "@tolbel/align";

const align = new Align({
  apiKey: process.env.ALIGNLAB_API_KEY!,
  environment: "production",
  debug: true, // Enable logging
  logLevel: "debug", // Set log level: 'error' | 'warn' | 'info' | 'debug'
});

// Logs will show:
// - Request details (URL, method)
// - Response details (status code)
// - Error details (status, code, message)

Note: Logging is disabled by default for production. When debug: false, the logger has zero overhead.

Using with Next.js App Router

// app/api/customers/route.ts
import { NextRequest, NextResponse } from "next/server";
import {
  Align,
  type CreateCustomerRequest,
  AlignValidationError,
} from "@tolbel/align";

const align = new Align({
  apiKey: process.env.ALIGNLAB_API_KEY!,
  environment: "production",
});

export async function POST(request: NextRequest) {
  try {
    const body = (await request.json()) as CreateCustomerRequest;

    // Validate required fields
    if (!body.email || !body.first_name || !body.last_name || !body.type) {
      return NextResponse.json(
        { error: "Missing required fields" },
        { status: 400 }
      );
    }

    const customer = await align.customers.create(body);

    return NextResponse.json(customer, { status: 201 });
  } catch (error) {
    if (error instanceof AlignValidationError) {
      return NextResponse.json(
        { error: "Validation failed", details: error.errors },
        { status: 400 }
      );
    }

    console.error("Error creating customer:", error);
    return NextResponse.json(
      { error: "Failed to create customer" },
      { status: 500 }
    );
  }
}

export async function GET(request: NextRequest) {
  try {
    const searchParams = request.nextUrl.searchParams;
    const page = parseInt(searchParams.get("page") || "1");
    const limit = parseInt(searchParams.get("limit") || "10");

    const customers = await align.customers.list(page, limit);

    return NextResponse.json(customers);
  } catch (error) {
    console.error("Error listing customers:", error);
    return NextResponse.json(
      { error: "Failed to list customers" },
      { status: 500 }
    );
  }
}

Using with Next.js Pages Router

// pages/api/customers/create.ts
import type { NextApiRequest, NextApiResponse } from "next";
import {
  Align,
  type CreateCustomerRequest,
  type Customer,
  AlignValidationError,
} from "@tolbel/align";

const align = new Align({
  apiKey: process.env.ALIGNLAB_API_KEY!,
  environment: "production",
});

type ErrorResponse = {
  error: string;
  details?: Record<string, string[]>;
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Customer | ErrorResponse>
) {
  if (req.method !== "POST") {
    return res.status(405).json({ error: "Method not allowed" });
  }

  try {
    const body = req.body as CreateCustomerRequest;

    const customer = await align.customers.create(body);

    return res.status(201).json(customer);
  } catch (error) {
    if (error instanceof AlignValidationError) {
      return res.status(400).json({
        error: "Validation failed",
        details: error.errors,
      });
    }

    console.error("Error creating customer:", error);
    return res.status(500).json({ error: "Failed to create customer" });
  }
}

Using with Express.js

import express, { Request, Response } from "express";
import {
  Align,
  type CreateCustomerRequest,
  AlignValidationError,
  AlignError,
} from "@tolbel/align";

const app = express();
const align = new Align({
  apiKey: process.env.ALIGNLAB_API_KEY!,
  environment: "production",
});

app.use(express.json());

// Create customer
app.post("/api/customers", async (req: Request, res: Response) => {
  try {
    const customer = await align.customers.create(
      req.body as CreateCustomerRequest
    );
    res.status(201).json(customer);
  } catch (error) {
    if (error instanceof AlignValidationError) {
      return res.status(400).json({
        error: "Validation failed",
        details: error.errors,
      });
    }
    if (error instanceof AlignError) {
      return res.status(error.status).json({
        error: error.message,
        code: error.code,
      });
    }
    res.status(500).json({ error: "Internal server error" });
  }
});

// Get customer
app.get("/api/customers/:id", async (req: Request, res: Response) => {
  try {
    const customer = await align.customers.get(req.params.id);
    res.json(customer);
  } catch (error) {
    if (error instanceof AlignError && error.status === 404) {
      return res.status(404).json({ error: "Customer not found" });
    }
    res.status(500).json({ error: "Internal server error" });
  }
});

// Create offramp transfer
app.post("/api/transfers/offramp", async (req: Request, res: Response) => {
  try {
    const { quote, transfer_purpose, destination_account_id } = req.body;

    // First create a quote
    const quoteResponse = await align.transfers.createOfframpQuote(quote);

    // Then execute the transfer
    const transfer = await align.transfers.createOfframpTransfer({
      transfer_purpose,
      destination_external_account_id: destination_account_id,
    });

    res.status(201).json({ quote: quoteResponse, transfer });
  } catch (error) {
    if (error instanceof AlignValidationError) {
      return res.status(400).json({
        error: "Validation failed",
        details: error.errors,
      });
    }
    res.status(500).json({ error: "Failed to create transfer" });
  }
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

Using with Fastify

import Fastify from "fastify";
import {
  Align,
  type CreateCustomerRequest,
  AlignValidationError,
} from "@tolbel/align";

const fastify = Fastify({ logger: true });

const align = new Align({
  apiKey: process.env.ALIGNLAB_API_KEY!,
  environment: "production",
});

fastify.post<{ Body: CreateCustomerRequest }>(
  "/api/customers",
  async (request, reply) => {
    try {
      const customer = await align.customers.create(request.body);
      return reply.status(201).send(customer);
    } catch (error) {
      if (error instanceof AlignValidationError) {
        return reply.status(400).send({
          error: "Validation failed",
          details: error.errors,
        });
      }
      return reply.status(500).send({ error: "Failed to create customer" });
    }
  }
);

fastify.listen({ port: 3000 }, (err) => {
  if (err) throw err;
});

Using with Hono

import { Hono } from "hono";
import {
  Align,
  type CreateCustomerRequest,
  AlignValidationError,
} from "@tolbel/align";

const app = new Hono();

const align = new Align({
  apiKey: process.env.ALIGNLAB_API_KEY!,
  environment: "production",
});

app.post("/api/customers", async (c) => {
  try {
    const body = await c.req.json<CreateCustomerRequest>();
    const customer = await align.customers.create(body);

    return c.json(customer, 201);
  } catch (error) {
    if (error instanceof AlignValidationError) {
      return c.json(
        {
          error: "Validation failed",
          details: error.errors,
        },
        400
      );
    }
    return c.json({ error: "Failed to create customer" }, 500);
  }
});

export default app;

Support


License

MIT


Contributing

Contributions are welcome! Please open an issue or submit a pull request at https://github.com/kibrukuture/align.


Changelog

See CHANGELOG.md for a list of changes.