# Claim Bonus
> Source: /api-reference/members/me/discover/claim-bonus
> Claim a one-time easter egg bonus
> Endpoint: POST /members/me/features/discover/bonuses/{code}/claim

## Overview [#overview]

Claim a one-time easter egg bonus. Easter eggs are special promotional bonuses that provide gift points to members. Each bonus can only be claimed once per member and awards non-tier-eligible gift points.

The system automatically validates:
- Bonus code exists and is active
- Member hasn't already claimed this bonus
- Awards appropriate points via the ledger system

## Authentication [#authentication]

Requires a valid Clerk JWT token in the Authorization header.

## Path Parameters [#path-parameters]

<ParamField path="code" type="string" required>
  The easter egg bonus code to claim
</ParamField>

<RequestExample>

```bash cURL
curl -X POST "http://localhost:3000/v1/members/me/features/discover/bonuses/WELCOME2024/claim" \
  -H "Authorization: Bearer YOUR_CLERK_JWT"
```

```typescript TypeScript
const claimBonus = async (code: string) => {
  const response = await fetch(`http://localhost:3000/v1/members/me/features/discover/bonuses/${code}/claim`, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_CLERK_JWT'
    }
  });
  
  return response.json();
};
```

```javascript Bruno
// From: bruno/Members (Surface)/Development/Discover/
POST {{host}}/members/me/features/discover/bonuses/{{bonusCode}}/claim
Authorization: Bearer {{clerkJwt}}
```

</RequestExample>

## Response [#response]

Returns the details of the claimed bonus including points awarded and timestamp.

<ResponseField name="eggCode" type="string" required>
  The bonus code that was claimed
</ResponseField>

<ResponseField name="displayName" type="string" required>
  Human-readable name of the bonus
</ResponseField>

<ResponseField name="pointsAwarded" type="number" required>
  Number of gift points awarded to the member
</ResponseField>

<ResponseField name="claimedAt" type="string" required>
  ISO 8601 timestamp when the bonus was claimed
</ResponseField>

<ResponseExample>

```json 200 Response - Successful Claim
{
  "eggCode": "WELCOME2024",
  "displayName": "Welcome to Journey 2024",
  "pointsAwarded": 5000,
  "claimedAt": "2024-04-20T15:30:45Z"
}
```

```json 404 Response - Invalid Code
{
  "statusCode": 404,
  "error": "NOT_FOUND", 
  "message": "Easter egg not found: INVALID_CODE"
}
```

```json 409 Response - Already Claimed
{
  "statusCode": 409,
  "error": "CONFLICT",
  "message": "This bonus has already been claimed"
}
```

</ResponseExample>

## Gift Points vs Earning Points [#gift-points-vs-earning-points]

### Gift Points Characteristics [#gift-points-characteristics]
- **Non-tier-eligible**: Don't count toward tier progression
- **Immediate availability**: Added to wallet immediately
- **Promotional source**: Marked with `giftSource: 'promotion'`
- **One-time only**: Each easter egg can only be claimed once per member

### Ledger Transaction [#ledger-transaction]
```typescript
// The service creates a gift transaction with metadata
const giftResult = await ledgerFacade.giftPoints({
  memberId,
  amount: egg.pointValue,
  reason: `Easter egg bonus: ${egg.friendlyName}`,
  giftSource: 'promotion',
  metadata: {
    easterEggCode: egg.code,
    easterEggId: egg.id,
    easterEggDisplayName: egg.displayName,
  },
});
```

## Use Cases [#use-cases]

### Claim Flow with Validation [#claim-flow-with-validation]
```typescript
const handleBonusClaim = async (code: string) => {
  try {
    // Check if already claimed
    const status = await getBonusStatus(code);
    if (status.claimed) {
      return { success: false, message: 'Bonus already claimed' };
    }
    
    // Attempt claim
    const result = await claimBonus(code);
    
    // Show success message
    showSuccessToast(`Claimed ${result.pointsAwarded.toLocaleString()} points!`);
    
    // Refresh wallet balance
    refreshWalletBalance();
    
    return { success: true, result };
  } catch (error) {
    if (error.status === 409) {
      return { success: false, message: 'Bonus already claimed' };
    }
    
    if (error.status === 404) {
      return { success: false, message: 'Invalid bonus code' };
    }
    
    return { success: false, message: 'Failed to claim bonus' };
  }
};
```

### Bonus Discovery UI [#bonus-discovery-ui]
```typescript
const BonusClaimCard = ({ bonus }) => {
  const [claiming, setClaiming] = useState(false);
  const [claimed, setClaimed] = useState(false);
  const [claimResult, setClaimResult] = useState(null);
  
  const handleClaim = async () => {
    setClaiming(true);
    
    try {
      const result = await claimBonus(bonus.code);
      setClaimResult(result);
      setClaimed(true);
      
      // Animate points addition
      animatePointsIncrease(result.pointsAwarded);
      
    } catch (error) {
      showError('Failed to claim bonus');
    } finally {
      setClaiming(false);
    }
  };
  
  return (
    <div className="bonus-card">
      <h3>{bonus.displayName}</h3>
      <div className="points-value">
        {bonus.pointValue.toLocaleString()} Points
      </div>
      
      {claimed ? (
        <div className="claimed-status">
          <CheckIcon />
          <span>Claimed {claimResult?.claimedAt && formatDate(claimResult.claimedAt)}</span>
        </div>
      ) : (
        <button 
          onClick={handleClaim}
          disabled={claiming}
          className="claim-button"
        >
          {claiming ? <Spinner /> : 'Claim Bonus'}
        </button>
      )}
    </div>
  );
};
```

### Wallet Integration [#wallet-integration]
```typescript
const trackBonusClaim = async (claimResult: ClaimEasterEggResult) => {
  // Track analytics event
  analytics.track('Bonus Claimed', {
    bonusCode: claimResult.eggCode,
    pointsAwarded: claimResult.pointsAwarded,
    claimedAt: claimResult.claimedAt
  });
  
  // Update local wallet state
  updateWalletBalance(currentBalance + claimResult.pointsAwarded);
  
  // Add transaction to activity feed
  addTransaction({
    type: 'gift',
    amount: claimResult.pointsAwarded,
    description: `Easter egg bonus: ${claimResult.displayName}`,
    createdAt: claimResult.claimedAt
  });
};
```

## Error Handling [#error-handling]

### Conflict Detection [#conflict-detection]
The service automatically prevents double-claiming by checking for existing claims before processing. If a member attempts to claim the same bonus twice, they receive a 409 Conflict error.

### Validation Flow [#validation-flow]
```typescript
const claimWithRetry = async (code: string, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await claimBonus(code);
    } catch (error) {
      if (error.status === 409 || error.status === 404) {
        // Don't retry for conflict or not found
        throw error;
      }
      
      if (attempt === maxRetries) {
        throw new Error('Failed to claim bonus after retries');
      }
      
      // Wait before retry
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
};
```

## Ledger Integration [#ledger-integration]

### Transaction Creation [#transaction-creation]
When a bonus is claimed, the system:
1. Validates the bonus code and member eligibility
2. Creates a gift points transaction in the ledger
3. Records the claim in the easter eggs system
4. Returns claim details to the client

### Metadata Tracking [#metadata-tracking]
```typescript
// Transaction metadata includes:
{
  easterEggCode: 'WELCOME2024',
  easterEggId: 123,
  easterEggDisplayName: 'Welcome to Journey 2024'
}
```

This metadata allows for easy tracking and reporting of bonus claims across the platform.

## Related Endpoints [#related-endpoints]

- [Get Bonus Status](/api-reference/members/me/discover/get-bonus-status) - Check if bonus is already claimed
- [Get Wallet Balance](/api-reference/members/wallet/get-balance) - View updated points balance
- [Get Wallet Activity](/api-reference/members/wallet/get-activity) - View claim transaction details
- [Get Earn More Gallery](/api-reference/members/me/discover/get-earn-more) - Properties with bonus campaigns
