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.
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/alignyarn add @tolbel/alignpnpm add @tolbel/alignbun add @tolbel/alignQuick 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
- Customers
- Virtual Accounts
- Transfers
- Cross-Chain Transfers
- External Accounts
- Wallets
- Webhooks
- Files
- Developers
- Error Handling
- TypeScript Types
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 KYCVirtual 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)
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", } );
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' } } */ } );
Complete Transfer (After Deposit)
const completedTransfer = await align.transfers.completeOfframpTransfer( customer.customer_id, transfer.id, { deposit_transaction_hash: "0x1234567890abcdef...", } );
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-signatureheader.
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
- Documentation: https://docs.alignlabs.dev
- API Reference: https://api.alignlabs.dev/docs
- GitHub: https://github.com/kibrukuture/align
- Issues: https://github.com/kibrukuture/align/issues
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.