AgentSkillsCN

Bridge Customers

通过Bridge.xyz API实现客户入驻、KYC身份验证与客户管理。支持个人客户与企业客户,涵盖身份验证、服务条款确认,以及客户状态的全程追踪。适用场景包括:生成KYC链接、管理客户生命周期、完成新用户注册、核实客户身份、处理身份认证授权,以及获取客户数据。

SKILL.md
--- frontmatter
name: Bridge Customers
description: Customer onboarding, KYC verification, and customer management with Bridge.xyz API. Handles individual and business customers, identity verification, terms of service acceptance, and customer status tracking. Use for: creating KYC links, managing customer lifecycle, onboarding new users, verifying identities, handling endorsements, and customer data retrieval.

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

  1. Store Customer ID - Always save the customer_id from KYC link creation
  2. Idempotency - Use Idempotency-Key for all customer operations
  3. Status Polling - Implement webhook listeners instead of polling
  4. Address Requirements - US customers require state field
  5. Endorsements - Different regions need different endorsements