BAP Profiles
Bitcoin Attestation Protocol (BAP) provides decentralized identity management on the Bitcoin blockchain. Sigma Auth automatically fetches and includes BAP profile data when available.
What is BAP?
BAP (Bitcoin Attestation Protocol) is a decentralized identity system that stores profile information on the Bitcoin blockchain. It allows users to create verified, immutable profiles linked to their Bitcoin identity.
BAP profiles follow an extended version of schema.org standards:
- Person - For individual user profiles
- Organization - For company/organization profiles
While maintaining schema.org compatibility, BAP extends these schemas with Bitcoin-specific fields:
rootAddress- Root Bitcoin (BSV) address for the identitycurrentAddress- Current signing address (may be rotated)idKey- BAP identity key (base58 encoded)paymail- Human-readable payment address (in identity data)- Additional blockchain-related attestations
This hybrid approach ensures BAP profiles are interoperable with standard web tools while supporting Bitcoin's unique requirements.
BAP Structure
The actual structure returned by api.sigmaidentity.com:
interface BAPProfile {
idKey: string; // BAP identity key (e.g. "A4PYmuKGG61WCjjBaRpuSEbqytG")
rootAddress: string; // Root Bitcoin address
currentAddress?: string; // Current signing address (may be rotated)
addresses?: Array<{ // Historical address chain
address: string;
txId: string;
block: number;
}>;
identity?: { // Schema.org Person/Organization data
"@context"?: string; // "https://schema.org"
"@type"?: string; // "Person" or "Organization"
alternateName?: string; // Display name/username
givenName?: string; // First name
familyName?: string; // Last name
description?: string; // Profile bio
image?: string; // Profile image URL
banner?: string; // Banner image URL
url?: string; // Website URL
email?: string; // Email address
paymail?: string; // Paymail address
[key: string]: unknown; // Additional schema.org fields
};
firstSeen?: number; // Block height when first seen
txHash?: string; // Transaction hash
}Getting BAP Profiles
Example: Demo User Lookup
Here's how Sigma Auth fetches BAP profiles internally. Using the demo identity Go8vCHAa4S6AhXKTABGpANiz35J:
# Direct API call to fetch BAP identity
curl -X POST https://api.sigmaidentity.com/api/v1/identity/get \
-H "Content-Type: application/json" \
-d '{"idKey": "Go8vCHAa4S6AhXKTABGpANiz35J"}'Response:
{
"status": "OK",
"result": {
"idKey": "Go8vCHAa4S6AhXKTABGpANiz35J",
"rootAddress": "1GXdTRDtQGKZ6yp2oUkDf8SuVhBf8Ww7ki",
"currentAddress": "1PHiKmPMqUSi5AWDYHMT3CvQ9rFAFnax8j",
"addresses": [
{
"address": "1PHiKmPMqUSi5AWDYHMT3CvQ9rFAFnax8j",
"txId": "dcc70a6b3a2ef400177f17c6b2d78b43015b13b608a4701c0245d1e4736686bb",
"block": 864381
}
],
"identity": {
"@type": "Person",
"@context": "https://schema.org",
"alternateName": "DemoUser",
"givenName": "Demo",
"familyName": "User",
"description": "Example BAP profile",
"image": "https://ordfs.network/abc123_62",
"banner": "https://ordfs.network/def456",
"email": "demo@example.com",
"url": "https://demo.example.com",
"paymail": "demo@example.com"
},
"firstSeen": 864381,
"txHash": "dcc70a6b3a2ef400177f17c6b2d78b43015b13b608a4701c0245d1e4736686bb"
}
}Sigma Auth includes this complete structure in the bap field of the /userinfo response.
From UserInfo Endpoint
BAP profiles are automatically included in the /userinfo endpoint response when available:
const response = await fetch('https://auth.sigmaidentity.com/userinfo', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
const userInfo = await response.json();
console.log(userInfo);Response with BAP Profile
When a BAP profile exists, the userinfo endpoint returns standard OIDC claims plus custom pubkey and bap fields:
{
"sub": "user_abc123def456",
"name": "JohnDoe",
"given_name": "John",
"family_name": "Doe",
"picture": "https://ordfs.network/abc123_62",
"pubkey": "03a1b2c3d4e5f6789...",
"bap": {
"idKey": "A4PYmuKGG61WCjjBaRpuSEbqytG",
"rootAddress": "1GXdTRDtQGKZ6yp2oUkDf8SuVhBf8Ww7ki",
"currentAddress": "1PHiKmPMqUSi5AWDYHMT3CvQ9rFAFnax8j",
"addresses": [
{
"address": "1PHiKmPMqUSi5AWDYHMT3CvQ9rFAFnax8j",
"txId": "dcc70a6b3a...",
"block": 864381
}
],
"identity": {
"@type": "Person",
"@context": "https://schema.org",
"alternateName": "JohnDoe",
"givenName": "John",
"familyName": "Doe",
"description": "Bitcoin developer",
"image": "https://ordfs.network/abc123_62",
"paymail": "john@example.com",
"url": "https://johndoe.com"
},
"firstSeen": 864381,
"txHash": "dcc70a6b3a..."
}
}Response without BAP Profile
When no BAP profile exists (user hasn't published on-chain):
{
"sub": "user_abc123def456",
"name": "1BitcoinAddress...",
"pubkey": "03a1b2c3d4e5f6789...",
"bap": null
}Profile Availability
When BAP Profiles Are Available
- ✅ User has created a BAP profile on the blockchain
- ✅ Profile is properly formatted and parseable
- ✅ Profile attestations are valid
- ✅ Sigma API can successfully fetch the profile
When BAP Profiles Are Not Available
- ❌ User has never created a BAP profile
- ❌ Profile data is corrupted or unparseable
- ❌ Network issues fetching from Sigma API
- ❌ Profile attestations are invalid
Handling Missing Profiles
Frontend Implementation
Always check for profile data availability:
function UserProfile({ userInfo }) {
const hasFullProfile = userInfo.bap?.identity;
if (hasFullProfile) {
return (
<div className="profile">
<img src={userInfo.picture} alt="Profile" />
<h2>{userInfo.name}</h2>
<p>{userInfo.bap.identity.description}</p>
{userInfo.bap.identity.paymail && (
<p>Paymail: {userInfo.bap.identity.paymail}</p>
)}
<p>BAP ID: {userInfo.bap.idKey}</p>
</div>
);
}
// Fallback for users without BAP profiles
return (
<div className="profile">
<div className="avatar-placeholder">
{userInfo.pubkey.slice(0, 2)}
</div>
<h2>Bitcoin User</h2>
<p>Public Key: {userInfo.pubkey}</p>
<button onClick={() => window.open('https://social.sigmaidentity.com/profile')}>
Create BAP Profile
</button>
</div>
);
}Backend Validation
Always validate profile data server-side:
function validateBAPProfile(bap: any): boolean {
// At minimum, idKey should always be present for valid BAP profiles
if (!bap?.idKey) {
return false;
}
// Validate rootAddress is present
if (!bap.rootAddress || typeof bap.rootAddress !== 'string') {
return false;
}
// Validate identity data if present
if (bap.identity?.image && !isValidURL(bap.identity.image)) {
return false;
}
return true;
}
function processUserInfo(userInfo: any) {
if (!validateBAPProfile(userInfo.bap)) {
// Handle invalid or missing BAP profile
return {
...userInfo,
bap: null // Explicitly set to null if invalid
};
}
return userInfo;
}Profile Creation Flow
Directing Users to Create Profiles
When users don't have BAP profiles, you can direct them to create one:
function CreateProfilePrompt({ publicKey }) {
const createProfileURL = `https://social.sigmaidentity.com/profile/create?key=${publicKey}`;
return (
<div className="profile-prompt">
<h3>Complete Your Bitcoin Identity</h3>
<p>Create a BAP profile to add your name, image, and other details to your Bitcoin identity.</p>
<button onClick={() => window.open(createProfileURL)}>
Create BAP Profile
</button>
</div>
);
}Profile Update Detection
Monitor for profile updates in your application:
async function refreshUserProfile(accessToken: string) {
try {
const response = await fetch('/userinfo', {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
const updatedUserInfo = await response.json();
// Check if profile has been updated
if (updatedUserInfo.profile?.name && !currentUser.profile?.name) {
// User has created a new BAP profile
showNotification('Profile updated successfully!');
updateUserState(updatedUserInfo);
}
} catch (error) {
console.error('Failed to refresh profile:', error);
}
}Common Profile Fields
Top-Level BAP Fields
| Field | Type | Description |
|---|---|---|
idKey | string | BAP identity key (always present) |
rootAddress | string | Root Bitcoin address |
currentAddress | string | Current signing address |
addresses | array | Historical address chain with txId and block |
identity | object | Schema.org Person/Organization data |
firstSeen | number | Block height when first seen |
txHash | string | Transaction hash |
Identity Object Fields (schema.org Person)
Fields inside bap.identity:
| Field | Type | Schema.org Property | Description |
|---|---|---|---|
@type | string | - | Schema.org type ("Person" or "Organization") |
@context | string | - | Schema.org context URL |
alternateName | string | Person.alternateName | Display name/username |
givenName | string | Person.givenName | First name |
familyName | string | Person.familyName | Last name |
description | string | Person.description | Profile bio/description |
image | string | Person.image | Profile image URL |
banner | string | - | Banner image URL |
email | string | Person.email | Email address |
url | string | Person.url | Website URL |
paymail | string | - | Paymail address (Bitcoin-specific) |
Extended Fields
BAP identity objects can include additional schema.org compatible fields:
{
"idKey": "A4PYmuKGG61WCjjBaRpuSEbqytG",
"rootAddress": "1GXdTRDtQGKZ6yp2oUkDf8SuVhBf8Ww7ki",
"currentAddress": "1PHiKmPMqUSi5AWDYHMT3CvQ9rFAFnax8j",
"addresses": [...],
"identity": {
"@type": "Person",
"@context": "https://schema.org",
"alternateName": "JohnDoe",
"givenName": "John",
"familyName": "Doe",
"description": "Senior Developer",
"image": "https://ordfs.network/abc123_62",
"url": "https://johndoe.com",
"jobTitle": "Senior Developer",
"worksFor": {
"@type": "Organization",
"name": "Bitcoin Corp",
"url": "https://bitcoincorp.com"
},
"address": {
"@type": "PostalAddress",
"addressLocality": "San Francisco",
"addressRegion": "CA",
"addressCountry": "US"
},
"sameAs": [
"https://twitter.com/johndoe",
"https://github.com/johndoe",
"https://linkedin.com/in/johndoe"
],
"knowsAbout": ["Bitcoin", "JavaScript", "Go"]
},
"firstSeen": 864381,
"txHash": "dcc70a6b3a..."
}Common Extended Fields
| Field | Schema.org Property | Description |
|---|---|---|
jobTitle | Person.jobTitle | Current job title |
worksFor | Person.worksFor | Organization/employer |
address | Person.address | Location/address |
sameAs | Person.sameAs | Links to social profiles |
knowsAbout | Person.knowsAbout | Skills/expertise |
alumniOf | Person.alumniOf | Educational institutions |
memberOf | Person.memberOf | Organizations/groups |
Organization Profiles
BAP also supports Organization profiles following schema.org/Organization:
{
"idKey": "B5QZnvLHH72XDkkCbTqvTFcszuH",
"rootAddress": "1OrgRootAddress123",
"currentAddress": "1OrgCurrentAddress456",
"addresses": [...],
"identity": {
"@type": "Organization",
"@context": "https://schema.org",
"alternateName": "BitcoinCorp",
"legalName": "Bitcoin Corporation Inc.",
"description": "Leading Bitcoin infrastructure provider",
"logo": "https://ordfs.network/logo123_62",
"url": "https://bitcoincorp.com",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Bitcoin St",
"addressLocality": "San Francisco",
"addressRegion": "CA",
"postalCode": "94105",
"addressCountry": "US"
},
"sameAs": [
"https://twitter.com/bitcoincorp",
"https://linkedin.com/company/bitcoincorp"
],
"foundingDate": "2019-01-01",
"founders": [{
"@type": "Person",
"name": "Satoshi Nakamoto"
}]
},
"firstSeen": 850000,
"txHash": "abc123def456..."
}Schema.org Benefits
Using schema.org standards for BAP profiles provides:
- Interoperability - Works with existing tools and services
- SEO Benefits - Search engines understand the data structure
- Rich Snippets - Enhanced display in search results
- Standardization - Consistent data format across applications
- Extensibility - Add any schema.org property as needed
Error Handling
Profile Fetch Errors
Handle cases where BAP profile fetching fails:
async function getUserInfo(accessToken: string) {
try {
const response = await fetch('/userinfo', {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const userInfo = await response.json();
// Validate BAP profile data
if (userInfo.bap && !userInfo.bap.idKey) {
console.warn('Invalid BAP profile data - missing idKey');
userInfo.bap = null;
}
return userInfo;
} catch (error) {
console.error('Failed to fetch user info:', error);
throw error;
}
}Network Resilience
Implement retry logic for profile fetching:
async function fetchProfileWithRetry(accessToken: string, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await getUserInfo(accessToken);
} catch (error) {
if (attempt === maxRetries) {
// Final attempt failed, return minimal profile
return {
sub: 'unknown',
name: 'Bitcoin User (Offline)',
pubkey: 'unknown',
bap: null
};
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}Best Practices
1. Always Provide Fallbacks
Never assume BAP profile data will be available:
const displayName = userInfo.name || 'Bitcoin User';
const profileImage = userInfo.picture || '/default-avatar.png';
const bapId = userInfo.bap?.idKey || null;2. Progressive Enhancement
Build your UI to work without BAP profiles, then enhance with profile data:
function UserCard({ userInfo }) {
return (
<div className="user-card">
{/* Always available */}
<div className="public-key">{userInfo.pubkey}</div>
<h3>{userInfo.name}</h3>
{/* Enhanced with BAP profile */}
{userInfo.picture && (
<img src={userInfo.picture} alt="Profile" />
)}
{userInfo.bap && (
<div className="bap-info">
<p>BAP ID: {userInfo.bap.idKey}</p>
{userInfo.bap.identity?.description && (
<p>{userInfo.bap.identity.description}</p>
)}
</div>
)}
</div>
);
}3. Cache Profile Data
Cache profile data to reduce API calls:
const profileCache = new Map();
async function getCachedProfile(publicKey: string) {
if (profileCache.has(publicKey)) {
return profileCache.get(publicKey);
}
const profile = await fetchUserProfile(publicKey);
profileCache.set(publicKey, profile);
// Cache for 5 minutes
setTimeout(() => {
profileCache.delete(publicKey);
}, 5 * 60 * 1000);
return profile;
}4. Validate Profile URLs
Always validate URLs in profile data:
function isValidURL(url: string): boolean {
try {
const parsed = new URL(url);
return parsed.protocol === 'https:' || parsed.protocol === 'http:';
} catch {
return false;
}
}
function sanitizeProfileImage(imageUrl: string): string {
if (!imageUrl || !isValidURL(imageUrl)) {
return '/default-avatar.png';
}
return imageUrl;
}Resources
- BAP Specification - Official BAP protocol documentation
- Sigma Identity - Create and manage BAP profiles
- BAP Explorer - Browse BAP profiles on the blockchain
- API Reference - Complete userinfo endpoint documentation