# Lookup Member
> Source: /api-reference/members/me/transfers/lookup-member
> Lookup member before transferring points
> Endpoint: POST /members/me/transfers/lookup

## Overview [#overview]

Lookup a member by email, phone, or member code before transferring points. Returns minimal public information to protect member privacy while allowing senders to verify they have the correct recipient.

## Authentication [#authentication]

Requires a valid Clerk JWT token in the Authorization header.

## Rate Limiting [#rate-limiting]

**Rate Limited**: 25 requests per hour per member to prevent abuse and protect member privacy.

## Request Body [#request-body]

<ParamField body="query" type="string" required>
  Search query - email address, phone number, or member code
</ParamField>

<ParamField body="type" type="string" required>
  Type of lookup: "email", "phone", or "memberCode"
</ParamField>

<RequestExample>

```bash cURL - Email Lookup
curl -X POST "http://localhost:3000/v1/members/me/transfers/lookup" \
  -H "Authorization: Bearer YOUR_CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "friend@example.com",
    "type": "email"
  }'
```

```bash cURL - Phone Lookup
curl -X POST "http://localhost:3000/v1/members/me/transfers/lookup" \
  -H "Authorization: Bearer YOUR_CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "+1234567890",
    "type": "phone"
  }'
```

```bash cURL - Member Code Lookup
curl -X POST "http://localhost:3000/v1/members/me/transfers/lookup" \
  -H "Authorization: Bearer YOUR_CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "MEMBER123",
    "type": "memberCode"
  }'
```

```typescript TypeScript
// Lookup by email
const lookupByEmail = async (email: string) => {
  const response = await fetch('http://localhost:3000/v1/members/me/transfers/lookup', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_CLERK_JWT',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      query: email,
      type: 'email'
    })
  });
  
  return response.json();
};

// Lookup by phone
const lookupByPhone = async (phone: string) => {
  const response = await fetch('http://localhost:3000/v1/members/me/transfers/lookup', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_CLERK_JWT',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      query: phone,
      type: 'phone'
    })
  });
  
  return response.json();
};
```

</RequestExample>

## Response [#response]

<ResponseField name="found" type="boolean" required>
  Whether a member was found matching the query
</ResponseField>

<ResponseField name="member" type="object">
  Member information (only present if found is true)
  
  <Expandable title="member object">
    <ResponseField name="displayName" type="string" required>
      Member's display name for verification
    </ResponseField>
    
    <ResponseField name="avatarUrl" type="string">
      Member's avatar URL (null if no avatar)
    </ResponseField>
    
    <ResponseField name="externalId" type="string" required>
      Member's external ID (ULID) for use in transfer creation
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseExample>

```json 200 Response - Member Found
{
  "found": true,
  "member": {
    "displayName": "John Smith",
    "avatarUrl": "https://cdn.example.com/avatars/john-smith.jpg",
    "externalId": "01ARZ3NDEKTSV4RRFFQ69G5FAV"
  }
}
```

```json 200 Response - Member Not Found
{
  "found": false
}
```

```json 200 Response - No Avatar
{
  "found": true,
  "member": {
    "displayName": "Jane Doe",
    "avatarUrl": null,
    "externalId": "01BRXT2YGJKP9MNPQRSTUVWXYZ"
  }
}
```

```json 400 Response - Self Lookup
{
  "statusCode": 400,
  "message": "You cannot transfer points to yourself.",
  "error": "SELF_LOOKUP"
}
```

```json 400 Response - Invalid Input
{
  "statusCode": 400,
  "message": "Validation failed",
  "error": "Bad Request"
}
```

```json 429 Response - Rate Limited
{
  "statusCode": 429,
  "message": "Rate limit exceeded. Please try again later.",
  "error": "Too Many Requests"
}
```

</ResponseExample>

## Lookup Types [#lookup-types]

### Email Lookup [#email-lookup]
- **Query Format**: Valid email address
- **Example**: `friend@example.com`
- **Case Sensitivity**: Case insensitive
- **Privacy**: Only finds verified email addresses

### Phone Lookup [#phone-lookup]
- **Query Format**: E.164 format phone number
- **Example**: `+1234567890`
- **Formatting**: Country code required
- **Privacy**: Only finds verified phone numbers

### Member Code Lookup [#member-code-lookup]
- **Query Format**: Member's unique code
- **Example**: `MEMBER123` or custom format
- **Case Sensitivity**: Case insensitive
- **Usage**: Public member codes for easy sharing

## Privacy Protection [#privacy-protection]

### Limited Information [#limited-information]
- **Display Name**: Only public display name shown
- **Avatar**: Public avatar URL (if set)
- **No Personal Data**: Email, phone, addresses not exposed
- **External ID**: Safe identifier for transfers

### Rate Limiting [#rate-limiting-1]
- **Limit**: 25 requests per hour per member
- **Purpose**: Prevent mass harvesting of member information
- **Reset**: Rolling 1-hour window
- **Bypass**: Not available (security measure)

### Self-Lookup Prevention [#self-lookup-prevention]
- **Blocked**: Cannot lookup your own account
- **Error**: Returns specific error message
- **Purpose**: Prevent self-transfer attempts

## Use Cases [#use-cases]

### Transfer Confirmation UI [#transfer-confirmation-ui]
```typescript
const confirmTransferRecipient = async (email: string) => {
  try {
    const response = await fetch('/api/members/me/transfers/lookup', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        query: email,
        type: 'email'
      })
    });
    
    const result = await response.json();
    
    if (result.found) {
      // Show confirmation dialog
      const confirmed = confirm(
        `Send points to ${result.member.displayName}?`
      );
      
      if (confirmed) {
        // Proceed with transfer using result.member.externalId
        return result.member.externalId;
      }
    } else {
      // Offer to send as gift transfer
      const sendAsGift = confirm(
        'Member not found. Send as gift transfer instead?'
      );
      
      if (sendAsGift) {
        return null; // Use email in gift transfer
      }
    }
  } catch (error) {
    console.error('Lookup failed:', error);
  }
};
```

### Auto-Complete Search [#auto-complete-search]
```typescript
const searchMembers = async (query: string, type: 'email' | 'phone' | 'memberCode') => {
  // Debounce to respect rate limits
  if (query.length < 3) return [];
  
  try {
    const response = await fetch('/api/members/me/transfers/lookup', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ query, type })
    });
    
    const result = await response.json();
    
    if (result.found) {
      return [{
        id: result.member.externalId,
        name: result.member.displayName,
        avatar: result.member.avatarUrl,
        type: 'member'
      }];
    }
    
    // Suggest gift transfer option
    return [{
      id: null,
      name: `Send as gift to ${query}`,
      avatar: null,
      type: 'gift'
    }];
  } catch (error) {
    return [];
  }
};
```

### Batch Lookup (Rate-Limited) [#batch-lookup-rate-limited]
```typescript
const lookupMultipleMembers = async (contacts: Array<{query: string, type: string}>) => {
  const results = [];
  const DELAY_MS = 150; // Respect rate limits
  
  for (const contact of contacts) {
    try {
      const response = await fetch('/api/members/me/transfers/lookup', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(contact)
      });
      
      const result = await response.json();
      results.push({ ...contact, ...result });
      
      // Delay between requests
      await new Promise(resolve => setTimeout(resolve, DELAY_MS));
    } catch (error) {
      results.push({ ...contact, found: false, error: true });
    }
  }
  
  return results;
};
```

## Error Handling [#error-handling]

### Rate Limit Management [#rate-limit-management]
```typescript
const lookupWithRetry = async (query: string, type: string, maxRetries = 2) => {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('/api/members/me/transfers/lookup', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query, type })
      });
      
      if (response.status === 429) {
        // Rate limited - wait and retry
        if (attempt < maxRetries) {
          const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff
          await new Promise(resolve => setTimeout(resolve, waitTime));
          continue;
        }
        throw new Error('Rate limit exceeded');
      }
      
      return response.json();
    } catch (error) {
      if (attempt === maxRetries) throw error;
    }
  }
};
```

## Validation [#validation]

### Query Format Validation [#query-format-validation]
```typescript
const validateLookupQuery = (query: string, type: string) => {
  const errors = [];
  
  if (!query || query.trim().length === 0) {
    errors.push('Query is required');
  }
  
  if (query.length > 255) {
    errors.push('Query too long (max 255 characters)');
  }
  
  switch (type) {
    case 'email':
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(query)) {
        errors.push('Invalid email format');
      }
      break;
      
    case 'phone':
      if (!/^\+?[1-9]\d{1,14}$/.test(query)) {
        errors.push('Invalid phone format (use E.164)');
      }
      break;
      
    case 'memberCode':
      if (!/^[A-Z0-9]{6,20}$/i.test(query)) {
        errors.push('Invalid member code format');
      }
      break;
      
    default:
      errors.push('Invalid lookup type');
  }
  
  return errors;
};
```

## Related Endpoints [#related-endpoints]

- [Create Transfer](/api-reference/members/me/transfers/create-transfer) - Use external ID from lookup for direct transfers
- [Get Transfer Limits](/api-reference/members/me/transfers/get-limits) - Check limits before showing transfer options
