Bridge Customers Management
Quick Reference
API Base
typescript
const BRIDGE_API_URL = 'https://api.bridge.xyz/v0'; const API_KEY = process.env.BRIDGE_API_KEY;
Create KYC Link (Customer Onboarding)
Individual Customer
typescript
interface CreateKycLinkRequest {
full_name: string;
email: string;
type: 'individual' | 'business';
endorsements?: string[];
redirect_uri?: string;
}
interface KycLinkResponse {
id: string;
customer_id: string;
full_name: string;
email: string;
type: 'individual' | 'business';
kyc_link: string;
kyc_status: 'not_started' | 'under_review' | 'incomplete' | 'approved' | 'rejected';
rejection_reasons: string[];
tos_link: string;
tos_status: 'pending' | 'approved';
created_at: string;
persona_inquiry_type: string;
}
async function createKycLink(request: CreateKycLinkRequest): Promise<KycLinkResponse> {
const response = await fetch(`${BRIDGE_API_URL}/kyc_links`, {
method: 'POST',
headers: {
'Api-Key': API_KEY,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify(request),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`KYC link creation failed: ${error.message}`);
}
return response.json();
}
// Usage
const kycLink = await createKycLink({
full_name: 'John Doe',
email: 'john.doe@example.com',
type: 'individual',
});
console.log('KYC Link:', kycLink.kyc_link);
console.log('Customer ID:', kycLink.customer_id);
console.log('KYC Status:', kycLink.kyc_status);
Business Customer
typescript
interface BusinessKycRequest {
full_name: string; // Business legal name
email: string;
type: 'business';
endorsements?: string[];
}
async function createBusinessCustomer(request: BusinessKycRequest) {
return createKycLink({
...request,
type: 'business',
});
}
// With endorsements
const businessKyc = await createBusinessCustomer({
full_name: 'Acme Corporation',
email: 'legal@acme.com',
endorsements: ['base', 'sepa'],
});
List Customers
typescript
interface Customer {
id: string;
first_name: string;
last_name: string;
email: string;
status: 'active' | 'pending' | 'suspended';
type: 'individual' | 'business';
has_accepted_terms_of_service: boolean;
address: {
street_line_1: string;
street_line_2?: string;
city: string;
postal_code: string;
state: string;
country: string;
};
rejection_reasons: string[];
requirements_due: string[];
future_requirements_due: string[];
endorsements: {
name: string;
status: 'approved' | 'incomplete' | 'pending';
additional_requirements?: string[];
}[];
beneficial_owners?: {
id: string;
email: string;
}[];
created_at: string;
updated_at: string;
}
async function listCustomers(): Promise<Customer[]> {
const response = await fetch(`${BRIDGE_API_URL}/customers`, {
headers: {
'Api-Key': API_KEY,
},
});
if (!response.ok) {
throw new Error('Failed to list customers');
}
const data = await response.json();
return data.data;
}
// Usage
const customers = await listCustomers();
customers.forEach(customer => {
console.log(`Customer: ${customer.first_name} ${customer.last_name}`);
console.log(`Status: ${customer.status}`);
console.log(`Type: ${customer.type}`);
console.log(`KYC: ${customer.endorsements.map(e => `${e.name}: ${e.status}`).join(', ')}`);
});
Get Customer Details
typescript
async function getCustomer(customerId: string): Promise<Customer> {
const response = await fetch(`${BRIDGE_API_URL}/customers/${customerId}`, {
headers: {
'Api-Key': API_KEY,
},
});
if (!response.ok) {
throw new Error(`Customer not found: ${customerId}`);
}
return response.json();
}
// Usage
const customer = await getCustomer('cust_ind_123');
console.log('Customer details:', customer);
Customer Status Tracking
typescript
interface KycStatusChecker {
async checkStatus(customerId: string): Promise<{
kycStatus: string;
tosStatus: string;
isApproved: boolean;
requirements: string[];
}> {
const customer = await getCustomer(customerId);
return {
kycStatus: customer.endorsements.find(e => e.name === 'base')?.status || 'unknown',
tosStatus: customer.has_accepted_terms_of_service ? 'approved' : 'pending',
isApproved: customer.endorsements.some(e => e.status === 'approved'),
requirements: [
...customer.requirements_due,
...customer.endorsements
.filter(e => e.status === 'incomplete')
.flatMap(e => e.additional_requirements || []),
],
};
}
}
// Complete onboarding flow
async function completeOnboarding(customerId: string) {
const status = await new KycStatusChecker().checkStatus(customerId);
if (status.isApproved) {
console.log('Customer fully onboarded!');
return true;
}
console.log('Pending requirements:', status.requirements);
return false;
}
Associated Persons (Business Customers)
typescript
interface AssociatedPerson {
id: string;
email: string;
first_name: string;
last_name: string;
has_ownership: boolean;
has_control: boolean;
is_signer: boolean;
title?: string;
attested_ownership_structure_at?: string;
relationship_established_at?: string;
created_at: number;
updated_at: number;
}
async function getAssociatedPersons(customerId: string): Promise<AssociatedPerson[]> {
const response = await fetch(`${BRIDGE_API_URL}/customers/${customerId}/associated_persons`, {
headers: {
'Api-Key': API_KEY,
},
});
if (!response.ok) {
throw new Error('Failed to get associated persons');
}
const data = await response.json();
return data.data;
}
async function updateAssociatedPerson(
customerId: string,
personId: string,
updates: Partial<AssociatedPerson>
): Promise<AssociatedPerson> {
const response = await fetch(
`${BRIDGE_API_URL}/customers/${customerId}/associated_persons/${personId}`,
{
method: 'PUT',
headers: {
'Api-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify(updates),
}
);
if (!response.ok) {
throw new Error('Failed to update associated person');
}
return response.json();
}
Common Patterns
Batch Customer Creation
typescript
async function createBulkCustomers(customers: CreateKycLinkRequest[]): Promise<KycLinkResponse[]> {
const results = await Promise.all(
customers.map(customer =>
createKycLink(customer).catch(error => ({
error: error.message,
email: customer.email,
}))
)
);
return results.filter((r): r is KycLinkResponse => 'id' in r);
}
Customer Onboarding State Machine
typescript
type OnboardingState =
| 'kyc_pending'
| 'kyc_under_review'
| 'kyc_incomplete'
| 'tos_pending'
| 'approved'
| 'rejected';
function determineOnboardingState(customer: Customer): OnboardingState {
if (customer.rejection_reasons.length > 0) {
return 'rejected';
}
const baseEndorsement = customer.endorsements.find(e => e.name === 'base');
if (baseEndorsement?.status === 'approved') {
return customer.has_accepted_terms_of_service ? 'approved' : 'tos_pending';
}
return baseEndorsement?.status === 'under_review'
? 'kyc_under_review'
: 'kyc_incomplete';
}
Error Handling
typescript
interface BridgeError {
code: string;
location: string;
name: string;
message: string;
}
function handleBridgeError(error: unknown): never {
if (error instanceof TypeError) {
throw error; // Rethrow network errors
}
const bridgeError = error as BridgeError;
switch (bridgeError.code) {
case 'required':
throw new Error(`Missing required field: ${bridgeError.name}`);
case 'invalid':
throw new Error(`Invalid value for ${bridgeError.name}: ${bridgeError.message}`);
default:
throw new Error(`Bridge API error: ${bridgeError.message}`);
}
}
Webhook Events
Relevant Event Types
- •
customer.created- New customer created - •
customer.updated- Customer information updated - •
customer.kyc_completed- KYC verification finished - •
customer.tos_accepted- Terms of service accepted
Best Practices
- •Store Customer ID - Always save the
customer_idfrom KYC link creation - •Idempotency - Use
Idempotency-Keyfor all customer operations - •Status Polling - Implement webhook listeners instead of polling
- •Address Requirements - US customers require state field
- •Endorsements - Different regions need different endorsements