API Reference
Deduct Points

Deduct Points

Remove points from a user's account when they redeem rewards.

⚠️

Critical: This operation must be idempotent. Check for duplicate yggRedemptionId values to prevent double deductions.

Endpoint Details

  • Method: POST
  • Path: /deduct-points-by-address
  • Content-Type: application/json
POST https://partner-api.example.com/deduct-points-by-address

Request Format

Headers

HeaderDescriptionExample
X-API-KEYYour API keyyour_api_key
X-API-REQUESTUnique UUID v701987d64-6519-747b-9200-beba98700464
X-API-SIGNATUREHMAC signature92aa703cc483ea2cc90488c05e699179...
Content-TypeMust be application/jsonapplication/json

Request Body

{
  "address": "0xabcd....a",
  "deductPoints": 1000,
  "yggRedemptionId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
FieldTypeRequiredDescription
addressstringUser's wallet address
deductPointsintegerPoints to deduct (positive integer)
yggRedemptionIdstringYGG-generated UUID for tracking

Response Format

Success Response

HTTP Status: 200 OK

Response Headers: Must include X-API-SIGNATURE

{
  "success": true,
  "partnerTransactionId": "txn_1234567890"
}

Required Fields

FieldTypeDescription
successbooleanMust be true
partnerTransactionIdstringYour unique transaction ID
🏷️

Important: Generate a unique partnerTransactionId for this deduction. This ID will be used for potential reversals.

Implementation Guidelines

Idempotency Check

🚨

Must implement: Check for duplicate yggRedemptionId to prevent double deductions.

// Pseudocode for idempotency check
const existingTransaction = await findTransactionByRedemptionId(yggRedemptionId);
 
if (existingTransaction) {
  return {
    success: false,
    errorCode: 'ERR-DUPLICATE-REQUEST',
    errorMessage: 'This redemption has already been processed'
  };
}

Balance Validation

Always verify sufficient balance before deduction:

if (user.points < deductPoints) {
  return {
    success: false,
    errorCode: 'ERR-INSUFFICIENT-POINTS',
    errorMessage: `User has ${user.points} points, cannot deduct ${deductPoints}`
  };
}

Example Implementation

app.post('/deduct-points-by-address', async (req, res) => {
  try {
    // Validate authentication
    if (!validateAuth(req.headers)) {
      return res.json({
        success: false,
        errorCode: 'ERR-AUTH-FAILED',
        errorMessage: 'Authentication failed'
      });
    }
    
    const { address, deductPoints, yggRedemptionId } = req.body;
    
    // Check for duplicate redemption
    const existing = await findByRedemptionId(yggRedemptionId);
    if (existing) {
      return res.json({
        success: false,
        errorCode: 'ERR-DUPLICATE-REQUEST',
        errorMessage: 'Redemption already processed'
      });
    }
    
    // Find user and validate balance
    const user = await findUserByWallet(address);
    if (!user) {
      return res.json({
        success: false,
        errorCode: 'ERR-USER-NOT-FOUND',
        errorMessage: 'User not found'
      });
    }
    
    if (user.points < deductPoints) {
      return res.json({
        success: false,
        errorCode: 'ERR-INSUFFICIENT-POINTS',
        errorMessage: 'Insufficient points'
      });
    }
    
    // Perform deduction
    const transactionId = await deductUserPoints(
      user.id, 
      deductPoints, 
      yggRedemptionId
    );
    
    res.json({
      success: true,
      partnerTransactionId: transactionId
    });
    
  } catch (error) {
    res.json({
      success: false,
      errorCode: 'ERR-INTERNAL',
      errorMessage: 'Internal server error'
    });
  }
});

Database Considerations

💾

Recommended: Store both yggRedemptionId and your partnerTransactionId for audit trails and potential reversals.

CREATE TABLE point_transactions (
  id SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL,
  transaction_type VARCHAR(20) NOT NULL, -- 'deduct' or 'revert'
  points INTEGER NOT NULL,
  ygg_redemption_id VARCHAR(36) UNIQUE NOT NULL,
  partner_transaction_id VARCHAR(50) UNIQUE NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);