Overview

Galxe’s B2B Credential API provides read-only access for:

  • User eligibility verification
  • Credential information retrieval
  • Access control validation
  • Requirement checking

Important: The B2B API is designed for verification and querying only. Credential management is handled through the Galxe Dashboard.

Understanding Credentials

Credentials are verification proofs that users earn through various activities:

  • EVM_ADDRESS: Ethereum-compatible wallet verification
  • SOLANA_ADDRESS: Solana wallet verification
  • TWITTER: Twitter-based verification
  • DISCORD: Discord membership verification
  • GITHUB: GitHub contribution verification
  • EMAIL: Email verification

Common Verification Methods

  • Social Media: TWITTER_FOLLOW, DISCORD_MEMBER, GITHUB_CONTRIBUTOR
  • Blockchain: CONTRACT_NFT_HOLDER, WALLET_BALANCE, SNAPSHOT_ORG
  • Interactive: QUIZ, SURVEY, VISIT_LINK
  • Custom: API, REST, GRAPHQL

Basic Credential Queries

Get Credential Information

query GetCredentialInfo($credId: ID!) {
  credential(id: $credId) {
    id
    name
    description
    credType
    credSource
    referenceLink
    itemCount
    lastUpdate
    syncStatus
    curatorSpace {
      id
      name
    }
    chain
  }
}

Response:

{
  "data": {
    "credential": {
      "id": "312969464",
      "name": "Twitter Follower Verification",
      "description": "Users who follow @galxe on Twitter",
      "credType": "TWITTER",
      "credSource": "TWITTER_FOLLOW",
      "referenceLink": "https://twitter.com/galxe",
      "itemCount": 150000,
      "lastUpdate": 1699200000,
      "syncStatus": "SYNCED",
      "curatorSpace": {
        "id": "40",
        "name": "BNB Chain"
      },
      "chain": "BSC"
    }
  }
}

Check User Eligibility

query CheckUserEligibility($credId: ID!, $address: String!) {
  credential(id: $credId) {
    id
    name
    eligible(address: $address)
    credType
    credSource
    syncStatus
  }
}

Response:

{
  "data": {
    "credential": {
      "id": "312969464",
      "name": "Twitter Follower Verification",
      "eligible": 1,
      "credType": "TWITTER",
      "credSource": "TWITTER_FOLLOW",
      "syncStatus": "SYNCED"
    }
  }
}

Eligibility Values:

  • 1: User is eligible
  • 0: User is not eligible

Quest-Specific Eligibility

query CheckQuestEligibility($credId: ID!, $address: String!, $campaignId: ID!) {
  credential(id: $credId) {
    eligible(address: $address, campaignId: $campaignId)
    name
    syncStatus
  }
}

Basic Implementation

Get Credential Information

async function getCredentialInfo(credId, accessToken) {
  const response = await fetch('https://graphigo-business.prd.galaxy.eco/query', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'access-token': accessToken
    },
    body: JSON.stringify({
      query: `query GetCredentialInfo($credId: ID!) {
        credential(id: $credId) {
          id
          name
          description
          credType
          credSource
          referenceLink
          itemCount
          lastUpdate
          syncStatus
          curatorSpace {
            id
            name
          }
          chain
        }
      }`,
      variables: { credId }
    })
  });
  
  const data = await response.json();
  return data.data.credential;
}

Verify User Eligibility

async function verifyUserEligibility(credId, userAddress, accessToken, campaignId = null) {
  const response = await fetch('https://graphigo-business.prd.galaxy.eco/query', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'access-token': accessToken
    },
    body: JSON.stringify({
      query: `query CheckUserEligibility($credId: ID!, $address: String!, $campaignId: ID) {
        credential(id: $credId) {
          id
          name
          eligible(address: $address, campaignId: $campaignId)
          credType
          credSource
          syncStatus
          lastSync
        }
      }`,
      variables: { credId, address: userAddress, campaignId }
    })
  });
  
  const data = await response.json();
  const credential = data.data.credential;
  
  return {
    credentialId: credential.id,
    credentialName: credential.name,
    isEligible: credential.eligible === 1,
    credentialType: credential.credType,
    credentialSource: credential.credSource,
    isActive: credential.syncStatus === 'SYNCED',
    lastSync: credential.lastSync,
    userAddress,
    campaignId
  };
}

Verify Multiple Credentials

async function verifyMultipleCredentials(credentialIds, userAddress, accessToken, campaignId = null) {
  const verificationPromises = credentialIds.map(credId => 
    verifyUserEligibility(credId, userAddress, accessToken, campaignId)
      .catch(error => ({
        credentialId: credId,
        error: error.message,
        isEligible: false,
        userAddress,
        campaignId
      }))
  );
  
  const results = await Promise.all(verificationPromises);
  
  const passed = results.filter(result => result.isEligible && !result.error);
  const failed = results.filter(result => !result.isEligible || result.error);
  
  return {
    userAddress,
    campaignId,
    totalChecked: credentialIds.length,
    passed: passed.length,
    failed: failed.length,
    allPassed: passed.length === credentialIds.length,
    results: { passed, failed }
  };
}

Access Control System

Simple Access Control

async function checkAccess(userAddress, requiredCredentials, accessToken, campaignId = null) {
  const verification = await verifyMultipleCredentials(
    requiredCredentials.map(req => req.credentialId),
    userAddress,
    accessToken,
    campaignId
  );
  
  // Check if all required credentials are met
  const requiredIds = requiredCredentials
    .filter(req => req.required !== false)
    .map(req => req.credentialId);
  
  const passedRequired = verification.results.passed.filter(result =>
    requiredIds.includes(result.credentialId)
  );
  
  const hasAccess = passedRequired.length === requiredIds.length;
  
  return {
    hasAccess,
    userAddress,
    campaignId,
    requirements: {
      total: requiredCredentials.length,
      required: requiredIds.length,
      satisfied: passedRequired.length
    },
    details: verification,
    missing: requiredCredentials.filter(req =>
      req.required !== false && 
      !passedRequired.some(passed => passed.credentialId === req.credentialId)
    )
  };
}

Usage Examples

Basic Verification

const accessToken = process.env.GALXE_ACCESS_TOKEN;

// Check single user eligibility
const result = await verifyUserEligibility(
  '312969464', // Twitter follower credential
  '0x1234567890123456789012345678901234567890',
  accessToken
);

console.log(`User eligible: ${result.isEligible}`);
console.log(`Credential: ${result.credentialName}`);
console.log(`Type: ${result.credentialType}`);

Multi-Credential Verification

// Check multiple requirements
const credentialIds = ['312969464', '312969465', '312969466'];
const userAddress = '0x1234567890123456789012345678901234567890';

const multiResult = await verifyMultipleCredentials(credentialIds, userAddress, accessToken);

console.log(`Passed: ${multiResult.passed}/${multiResult.totalChecked} credentials`);
console.log(`Access granted: ${multiResult.allPassed}`);

if (!multiResult.allPassed) {
  console.log('Failed credentials:', multiResult.results.failed.map(f => f.credentialName || f.credentialId));
}

Quest-Specific Verification

// Check eligibility for specific quest
const questResult = await verifyUserEligibility(
  '312969464',
  userAddress,
  accessToken,
  'GChdWUjXX3' // Quest ID
);

console.log(`Quest eligible: ${questResult.isEligible}`);

Access Control System

// Define access requirements
const requirements = [
  { credentialId: '312969464', description: 'Twitter Follower', required: true },
  { credentialId: '312969465', description: 'Discord Member', required: true },
  { credentialId: '312969466', description: 'NFT Holder', required: false }
];

// Check user access
const accessCheck = await checkAccess(userAddress, requirements, accessToken);

if (accessCheck.hasAccess) {
  console.log('Access granted!');
  console.log(`Satisfied ${accessCheck.requirements.satisfied}/${accessCheck.requirements.required} required credentials`);
} else {
  console.log('Access denied. Missing requirements:');
  accessCheck.missing.forEach(req => {
    console.log(`- ${req.description}`);
  });
}

Common Patterns

User Onboarding Flow

async function onboardUser(userAddress, accessToken) {
  const requiredCredentials = [
    { credentialId: '312969464', description: 'Twitter Follower', required: true },
    { credentialId: '312969465', description: 'Discord Member', required: true }
  ];
  
  const verification = await verifyMultipleCredentials(
    requiredCredentials.map(r => r.credentialId), 
    userAddress,
    accessToken
  );
  
  if (verification.allPassed) {
    return { 
      status: 'approved', 
      message: 'Welcome! All requirements met.' 
    };
  } else {
    const missing = requiredCredentials.filter(req =>
      !verification.results.passed.some(p => p.credentialId === req.credentialId)
    );
    
    return { 
      status: 'pending', 
      message: `Please complete: ${missing.map(m => m.description).join(', ')}`,
      missingCredentials: missing
    };
  }
}

Access Gate Implementation

async function checkGateAccess(userAddress, requiredCredId, accessToken, questId = null) {
  const result = await verifyUserEligibility(requiredCredId, userAddress, accessToken, questId);
  
  return {
    allowed: result.isEligible,
    reason: result.isEligible ? 'Access granted' : `Missing: ${result.credentialName}`,
    credentialInfo: {
      name: result.credentialName,
      type: result.credentialType,
      source: result.credentialSource
    }
  };
}

Sync Status Handling

Check Credential Sync Status

Credentials have different sync statuses that affect reliability:

  • SYNCED: Data is current and reliable
  • SYNCING: Data is being updated, may be stale
async function waitForSync(credId, accessToken, maxWaitMs = 60000) {
  const startTime = Date.now();
  
  while (Date.now() - startTime < maxWaitMs) {
    const info = await getCredentialInfo(credId, accessToken);
    
    if (info.syncStatus === 'SYNCED') {
      return { synced: true, waited: Date.now() - startTime };
    }
    
    await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
  }
  
  return { synced: false, waited: maxWaitMs };
}

// Usage
const syncCheck = await waitForSync('312969464', accessToken);
if (syncCheck.synced) {
  console.log(`Credential synced after ${syncCheck.waited}ms`);
  // Now safe to check eligibility
} else {
  console.log('Credential still syncing, results may be stale');
}

Best Practices

  1. Caching: Cache eligibility results for 5-10 minutes to reduce API calls
  2. Sync Status: Check syncStatus field before relying on eligibility results
  3. Error Handling: Always handle network errors and invalid credential IDs
  4. Quest Context: Use campaignId parameter for quest-specific verifications
  5. Rate Limiting: Implement delays between batch requests (200-500ms)

Understanding Sync Status

  • Always check the syncStatus field in responses
  • Only rely on results when status is SYNCED
  • For critical verifications, consider waiting for sync completion
  • Cache results appropriately based on sync status

Next Steps