Skip to main content
POST
https://api-mainnet.onzks.com
/
v1
/
achievements
/
:identity
/
claim
/
:achievementId
Claim Achievement
curl --request POST \
  --url https://api-mainnet.onzks.com/v1/achievements/:identity/claim/:achievementId \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "proof": {
    "proof.requirements": {},
    "proof.timestamp": "<string>",
    "proof.chainId": 123,
    "proof.transactionHash": "<string>"
  },
  "signature": "<string>",
  "metadata": {
    "metadata.source": "<string>",
    "metadata.notes": "<string>",
    "metadata.tags": [
      {}
    ]
  }
}
'
{
  "success": true,
  "message": "<string>",
  "address": "<string>",
  "zksId": "<string>",
  "achievement": {
    "id": "<string>",
    "title": "<string>",
    "description": "<string>",
    "category": "<string>",
    "rarity": "<string>",
    "points": 123,
    "scoreBonus": 123,
    "icon": "<string>"
  },
  "rewards": {
    "points": 123,
    "scoreBonus": 123,
    "nftTokenId": "<string>",
    "nftContract": "<string>",
    "nftMetadata": "<string>"
  },
  "totalPoints": 123,
  "totalScores": 123,
  "claimed": true,
  "celebrate": true,
  "timestamp": "<string>"
}

Overview

Claim an achievement that has been earned by a user. This endpoint verifies that the user has met all requirements, mints the achievement NFT (if applicable), and awards points and score bonuses.
Use this endpoint to allow users to claim their earned achievements and receive their rewards. Always verify the user has earned the achievement before calling this endpoint.

Parameters

identity
string
required
User identity (ZKS ID or wallet address)
ZKS ID is recommended for better performance and user experience
achievementId
string
required
Unique identifier of the achievement to claim

Request Body

proof
object
Proof of achievement completion
signature
string
Cryptographic signature proving ownership of the identity
metadata
object
Additional metadata for the claim

Response

success
boolean
Indicates if the claim was successful
message
string
Success message
address
string
Resolved wallet address
zksId
string
ZKS ID if available, null otherwise
achievement
object
Claimed achievement details
rewards
object
Rewards received from claiming
totalPoints
number
User’s total points after claiming
totalScores
number
User’s total ZKScore after claiming
claimed
boolean
Whether the achievement was successfully claimed
celebrate
boolean
Whether this is a special achievement worth celebrating
timestamp
string
ISO 8601 timestamp of the claim

Examples

curl -X POST "https://api.onzks.com/v1/achievements/alice.zks/claim/defi-pioneer" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "proof": {
      "requirements": {
        "uniqueProtocols": 50,
        "minimumChains": 3,
        "minimumVolume": "100000"
      },
      "timestamp": "2024-01-20T15:45:00Z",
      "chainId": 1,
      "transactionHash": "0x1234567890abcdef..."
    },
    "signature": "0xabcdef1234567890...",
    "metadata": {
      "source": "defi_protocol",
      "notes": "Completed DeFi journey across multiple chains",
      "tags": ["defi", "pioneer", "multi-chain"]
    }
  }'

Response Example

{
  "success": true,
  "message": "Achievement claimed successfully!",
  "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "zksId": "alice.zks",
  "achievement": {
    "id": "defi-pioneer",
    "title": "DeFi Pioneer",
    "description": "Interact with 50+ different DeFi protocols across multiple chains",
    "category": "defi",
    "rarity": "legendary",
    "points": 5000,
    "scoreBonus": 500,
    "icon": "https://cdn.onzks.com/achievements/defi-pioneer.png"
  },
  "rewards": {
    "points": 5000,
    "scoreBonus": 500,
    "nftTokenId": "12345",
    "nftContract": "0x1234567890abcdef...",
    "nftMetadata": "https://api.onzks.com/v1/nft/metadata/12345"
  },
  "totalPoints": 25000,
  "totalScores": 2500,
  "claimed": true,
  "celebrate": true,
  "timestamp": "2024-01-20T15:45:00Z"
}

Use Cases

1. Achievement Claiming Flow

Complete achievement claiming process:
async function claimAchievementFlow(identity, achievementId) {
  try {
    // 1. Check if user has earned the achievement
    const progress = await getAchievementProgress(identity, achievementId);
    
    if (progress.progress.earned) {
      console.log('Achievement already claimed');
      return;
    }
    
    if (progress.progress.percentage < 100) {
      console.log('Achievement not yet earned');
      return;
    }
    
    // 2. Generate proof
    const proof = await generateAchievementProof(identity, achievementId);
    
    // 3. Get user signature
    const signature = await getUserSignature(identity, achievementId);
    
    // 4. Claim the achievement
    const result = await claimAchievement(identity, achievementId, proof, signature);
    
    // 5. Show celebration
    if (result.celebrate) {
      showCelebration(result.achievement);
    }
    
    return result;
  } catch (error) {
    console.error('Failed to claim achievement:', error);
    throw error;
  }
}

2. Batch Achievement Claims

Claim multiple achievements at once:
async function claimMultipleAchievements(identity, achievementIds) {
  const results = [];
  
  for (const achievementId of achievementIds) {
    try {
      const result = await claimAchievementFlow(identity, achievementId);
      results.push({ achievementId, success: true, result });
    } catch (error) {
      results.push({ achievementId, success: false, error: error.message });
    }
  }
  
  const successful = results.filter(r => r.success);
  const failed = results.filter(r => !r.success);
  
  console.log(`Successfully claimed ${successful.length} achievements`);
  if (failed.length > 0) {
    console.log(`Failed to claim ${failed.length} achievements`);
  }
  
  return results;
}

3. Achievement Verification

Verify achievement before claiming:
async function verifyAchievement(identity, achievementId) {
  const progress = await getAchievementProgress(identity, achievementId);
  
  const verification = {
    canClaim: progress.progress.percentage >= 100 && !progress.progress.earned,
    requirements: progress.progress.requirements,
    current: progress.progress.current,
    required: progress.progress.required,
    percentage: progress.progress.percentage
  };
  
  if (verification.canClaim) {
    console.log('✅ Achievement ready to claim');
  } else {
    console.log('❌ Achievement not ready to claim');
    console.log(`Progress: ${verification.percentage}%`);
  }
  
  return verification;
}

4. Celebration System

Show achievement celebrations:
function showCelebration(achievement) {
  const celebration = {
    title: achievement.title,
    rarity: achievement.rarity,
    points: achievement.points,
    scoreBonus: achievement.scoreBonus
  };
  
  // Show celebration modal
  const modal = document.createElement('div');
  modal.className = 'celebration-modal';
  modal.innerHTML = `
    <div class="celebration-content">
      <h2>🎉 Achievement Unlocked!</h2>
      <h3>${celebration.title}</h3>
      <p>Rarity: ${celebration.rarity}</p>
      <p>Points: ${celebration.points}</p>
      <p>Score Bonus: ${celebration.scoreBonus}</p>
      <button onclick="closeCelebration()">Awesome!</button>
    </div>
  `;
  
  document.body.appendChild(modal);
  
  // Auto-close after 5 seconds
  setTimeout(() => {
    closeCelebration();
  }, 5000);
}

function closeCelebration() {
  const modal = document.querySelector('.celebration-modal');
  if (modal) {
    modal.remove();
  }
}

5. NFT Integration

Handle NFT minting for achievements:
async function handleAchievementNFT(result) {
  if (result.rewards.nftTokenId) {
    const nft = {
      tokenId: result.rewards.nftTokenId,
      contract: result.rewards.nftContract,
      metadata: result.rewards.nftMetadata
    };
    
    console.log('NFT minted for achievement:', nft);
    
    // Add to user's NFT collection
    await addToUserCollection(result.address, nft);
    
    // Show NFT in UI
    displayAchievementNFT(nft);
  }
}

function displayAchievementNFT(nft) {
  const nftElement = document.createElement('div');
  nftElement.className = 'achievement-nft';
  nftElement.innerHTML = `
    <img src="${nft.metadata}" alt="Achievement NFT" />
    <p>Token ID: ${nft.tokenId}</p>
    <p>Contract: ${nft.contract}</p>
  `;
  
  document.getElementById('nft-collection').appendChild(nftElement);
}

Best Practices

1. Verify Before Claiming

Always verify the user has earned the achievement:
async function safeClaimAchievement(identity, achievementId) {
  // Verify achievement is earned
  const progress = await getAchievementProgress(identity, achievementId);
  
  if (!progress.progress.earned && progress.progress.percentage < 100) {
    throw new Error('Achievement not yet earned');
  }
  
  if (progress.progress.earned) {
    throw new Error('Achievement already claimed');
  }
  
  // Proceed with claim
  return await claimAchievement(identity, achievementId, proof, signature);
}

2. Handle Errors Gracefully

Implement proper error handling:
async function claimWithErrorHandling(identity, achievementId) {
  try {
    const result = await claimAchievement(identity, achievementId, proof, signature);
    return { success: true, result };
  } catch (error) {
    if (error.message.includes('already claimed')) {
      return { success: false, error: 'Achievement already claimed' };
    } else if (error.message.includes('not earned')) {
      return { success: false, error: 'Achievement not yet earned' };
    } else if (error.message.includes('invalid signature')) {
      return { success: false, error: 'Invalid signature' };
    } else {
      return { success: false, error: 'Unknown error occurred' };
    }
  }
}

3. Batch Processing

Process multiple claims efficiently:
async function batchClaimAchievements(identity, achievementIds) {
  const batchSize = 5; // Process 5 at a time
  const results = [];
  
  for (let i = 0; i < achievementIds.length; i += batchSize) {
    const batch = achievementIds.slice(i, i + batchSize);
    const batchPromises = batch.map(id => claimAchievement(identity, id, proof, signature));
    
    const batchResults = await Promise.allSettled(batchPromises);
    results.push(...batchResults);
    
    // Wait between batches to avoid rate limits
    if (i + batchSize < achievementIds.length) {
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
  
  return results;
}

4. Progress Tracking

Track claim progress:
function trackClaimProgress(identity, achievementId) {
  const startTime = Date.now();
  
  return {
    start: () => {
      console.log('Starting achievement claim...');
    },
    complete: (result) => {
      const duration = Date.now() - startTime;
      console.log(`Achievement claimed in ${duration}ms`);
      console.log(`Points awarded: ${result.rewards.points}`);
    },
    error: (error) => {
      const duration = Date.now() - startTime;
      console.error(`Claim failed after ${duration}ms:`, error);
    }
  };
}

Troubleshooting

”Achievement not earned”

Cause: User hasn’t met the requirements for the achievement. Solution:
  • Check the achievement requirements
  • Verify the user’s progress
  • Wait for the user to complete the requirements

”Achievement already claimed”

Cause: User has already claimed this achievement. Solution:
  • Check if the achievement is already in the user’s collection
  • Don’t attempt to claim the same achievement twice

”Invalid signature”

Cause: The signature doesn’t match the user’s identity. Solution:
  • Verify the signature is from the correct wallet
  • Check that the message was signed correctly
  • Ensure the user is signing with the right account

”Proof verification failed”

Cause: The proof doesn’t meet the achievement requirements. Solution:
  • Verify all requirements are met
  • Check that the proof data is accurate
  • Ensure the timestamp is recent

Rate Limits

Achievement claim requests are subject to rate limits:
  • Free tier: 10 claims per minute
  • Starter tier: 50 claims per minute
  • Professional tier: 200 claims per minute
  • Enterprise tier: Custom limits
Implement queuing for batch claims to avoid rate limits.