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:
Popular Credential Types
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
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
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
- Caching: Cache eligibility results for 5-10 minutes to reduce API calls
- Sync Status: Check
syncStatus
field before relying on eligibility results
- Error Handling: Always handle network errors and invalid credential IDs
- Quest Context: Use
campaignId
parameter for quest-specific verifications
- 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