Access Control API Reference
Base URL
https://auth.sigmaidentity.comAll API requests require authentication via session cookie obtained through the OAuth flow.
Authentication
Include your session cookie in requests:
Cookie: better-auth.session_token=YOUR_SESSION_TOKENIn production, the cookie name is __Secure-session_token (HTTPS only).
Endpoints
POST /api/wallet/connect
Connect a wallet address to the authenticated user's BAP identity.
Request
BSV Native Wallet (bitcoin-auth):
POST /api/wallet/connect
Content-Type: application/json
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN
{
"authToken": "03abc123...|bsm|2025-01-15T10:30:00.000Z|/api/wallet/connect|abc123..."
}The authToken is a self-contained bitcoin-auth signature:
{pubkey}|{scheme}|{timestamp}|{requestPath}|{signature}Response:
{
"success": true,
"walletAddress": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
"pubkey": "03abc123...",
"connectedAt": "2025-01-15T10:30:00.000Z"
}Errors
// Not authenticated
{
"error": "unauthorized",
"message": "Authentication required"
}
// Invalid authToken
{
"error": "invalid_token",
"message": "Malformed auth token"
}
// Invalid signature
{
"error": "invalid_signature",
"message": "Signature verification failed"
}GET /api/wallet/connect
List all wallet addresses connected to the authenticated user.
Request
GET /api/wallet/connect
Cookie: better-auth.session_token=YOUR_SESSION_TOKENResponse
{
"wallets": [
{
"address": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
"provider": "yours",
"connectionMethod": "bsm",
"isPrimary": false,
"connectedAt": "2025-01-15T10:30:00.000Z",
"lastVerified": "2025-01-15T10:30:00.000Z"
},
{
"address": "1J1AZwJyk9LU6tyR1ku1Ps2rs5wrLXBp2G",
"provider": "1sat",
"connectionMethod": "bsm",
"isPrimary": false,
"connectedAt": "2025-01-14T08:15:00.000Z",
"lastVerified": "2025-01-15T09:00:00.000Z"
}
]
}DELETE /api/wallet/connect?address={walletAddress}
Remove a wallet connection.
Request
DELETE /api/wallet/connect?address=139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ
Cookie: better-auth.session_token=YOUR_SESSION_TOKENResponse
{
"success": true,
"message": "Wallet disconnected successfully"
}POST /api/wallet/verify-ownership
Verify NFT ownership with optional threshold check.
Request
Basic Ownership Check:
POST /api/wallet/verify-ownership
Content-Type: application/json
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN
{
"origin": "1611d956f397caa80b56bc148b4bce87b54f39b234aeca4668b4d5a7785eb9fa_0"
}With Threshold:
POST /api/wallet/verify-ownership
Content-Type: application/json
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN
{
"origin": "1611d956f397caa80b56bc148b4bce87b54f39b234aeca4668b4d5a7785eb9fa_0",
"minCount": 50 // Require at least 50 NFTs
}Alternative: Collection Parameter:
POST /api/wallet/verify-ownership
Content-Type: application/json
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN
{
"collection": "collection-identifier",
"minCount": 10
}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
origin | string | Yes* | Collection ID (from NFT metadata collectionId field) |
collection | string | Yes* | Alternative collection identifier |
minCount | number | No | Minimum NFTs required (default: 1) |
*Either origin or collection must be provided.
Response
Ownership Confirmed:
{
"owns": true,
"count": 268, // Total NFTs owned from this collection
"nfts": [ // Array of NFT objects (first 100)
{
"txid": "899d447b157663d64ddce920037f8b764d2fed7095d8f51289da705c54ed2fbf",
"vout": 0,
"outpoint": "899d447b157663d64ddce920037f8b764d2fed7095d8f51289da705c54ed2fbf_0",
"satoshis": 1,
"accSats": "0",
"height": 871519,
"idx": "6783",
"owner": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
"spend": "",
"spend_height": null,
"spend_idx": null,
"origin": {
"outpoint": "8328b6dba103957f7f1d21b5bdfb5762e9e729800611d4c45f0485a16ce4987b_37",
"data": {
"map": {
"app": "Bubblemint",
"name": "Pixel Foxes #2031290",
"type": "ord",
"subType": "collectionItem",
"subTypeData": {
"collectionId": "1611d956f397caa80b56bc148b4bce87b54f39b234aeca4668b4d5a7785eb9fa_0"
}
}
},
"num": "0854022:266:0"
},
"data": null
}
// ... up to 99 more NFTs
]
}No NFTs Found:
{
"owns": false,
"count": 0
}Below Threshold:
// User has 25 NFTs, but minCount is 50
{
"owns": false,
"count": 25
}No Wallets Connected:
{
"owns": false,
"count": 0,
"message": "No wallets connected"
}Errors
// Not authenticated
{
"error": "unauthorized",
"message": "Authentication required"
}
// Missing required parameter
{
"error": "invalid_request",
"message": "Must provide either origin or collection"
}
// Internal error
{
"error": "internal_error",
"message": "Failed to verify ownership"
}GET /api/wallet/nfts
List all NFTs owned by the authenticated user across all connected wallets.
Request
GET /api/wallet/nfts?refresh=true
Cookie: better-auth.session_token=YOUR_SESSION_TOKENQuery Parameters
| Parameter | Type | Description |
|---|---|---|
refresh | boolean | Force blockchain refresh (default: false) |
Response
{
"wallets": [
{
"address": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
"nfts": [
{ /* ... NFT object ... */ }
],
"count": 268
},
{
"address": "1J1AZwJyk9LU6tyR1ku1Ps2rs5wrLXBp2G",
"nfts": [
{ /* ... NFT object ... */ }
],
"count": 100
}
],
"totalNFTs": 368,
"addresses": [
"139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
"1J1AZwJyk9LU6tyR1ku1Ps2rs5wrLXBp2G"
]
}GET /api/wallet/address
Get receiving addresses for the authenticated user.
Request
GET /api/wallet/address
Cookie: better-auth.session_token=YOUR_SESSION_TOKENResponse
{
"primaryAddress": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
"addresses": [
{
"address": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
"provider": "yours",
"connectionMethod": "bsm",
"isPrimary": true,
"connectedAt": "2025-01-15T10:30:00.000Z"
},
{
"address": "1J1AZwJyk9LU6tyR1ku1Ps2rs5wrLXBp2G",
"provider": "1sat",
"connectionMethod": "bsm",
"isPrimary": false,
"connectedAt": "2025-01-14T08:15:00.000Z"
}
]
}POST /api/wallet/set-primary
Set the primary receiving address for NFT purchases.
Request
POST /api/wallet/set-primary
Content-Type: application/json
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN
{
"walletAddress": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ"
}Response
{
"success": true,
"primaryAddress": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ"
}Technical Details
Pagination
The /api/wallet/verify-ownership endpoint automatically paginates through all NFTs:
- Fetches 100 NFTs per page from the blockchain
- Continues until no more results
- Returns accurate total count across all pages
- First 100 NFTs included in
nftsarray
Collection Identification
Collections are identified by the collectionId field in NFT metadata:
{
"origin": {
"data": {
"map": {
"subTypeData": {
"collectionId": "1611d956f397caa80b56bc148b4bce87b54f39b234aeca4668b4d5a7785eb9fa_0"
}
}
}
}
}The origin parameter in API requests should match this collectionId value.
Bitcoin-Auth Integration
Wallet connections use the bitcoin-auth library for signature verification:
Auth Token Format:
{pubkey}|{scheme}|{timestamp}|{requestPath}|{signature}Supported Schemes:
bsm- Bitcoin Signed Message (broader compatibility)brc77- BRC-77 standard
Verification:
- Signature is verified using the public key in the token
- Timestamp must be within 5 minutes of current time
- Request path must match the endpoint being accessed
Blockchain Data Source
NFT data is fetched from:
https://ordinals.gorillapool.io/api/txos/address/{address}/unspentParameters:
limit=100- NFTs per pageoffset={n}- Pagination offsetbsv20=false- Exclude BSV-20 tokensorigins=false- Origin data inclusionrefresh=true- Force blockchain refresh
Error Codes
| Code | Description |
|---|---|
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Invalid or missing authentication |
| 500 | Internal Server Error - Server-side error |
Error Response Format
{
"error": "error_code",
"message": "Human-readable error description"
}Best Practices
Caching
Cache verification results to reduce API load:
// Cache ownership checks for 5 minutes
const cacheKey = `nft-ownership-${userId}-${collectionId}`;
let result = cache.get(cacheKey);
if (!result) {
result = await fetch('/api/wallet/verify-ownership', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ origin: collectionId })
}).then(r => r.json());
cache.set(cacheKey, result, 300); // 5 minutes TTL
}Error Handling
Always handle API errors gracefully:
async function verifyOwnership(origin, minCount = 1) {
try {
const response = await fetch('/api/wallet/verify-ownership', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ origin, minCount })
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || `HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Verification failed:', error);
// Fallback: deny access
return { owns: false, count: 0 };
}
}Multi-Threshold Optimization
Fetch count once, check multiple thresholds locally:
// ✅ Good: Single API call
const { count } = await verifyOwnership(collectionId);
const tiers = {
bronze: count >= 10,
silver: count >= 50,
gold: count >= 100
};
// ❌ Bad: Multiple API calls
const bronze = await verifyOwnership(collectionId, 10);
const silver = await verifyOwnership(collectionId, 50);
const gold = await verifyOwnership(collectionId, 100);Session Management
Session cookies are HTTP-only and secure:
- Development:
better-auth.session_token(HTTP) - Production:
__Secure-session_token(HTTPS only) - Expires: After 7 days of inactivity
- Renewal: Automatic on API requests
Example Workflows
Complete NFT Verification Flow
// 1. User authenticates via OAuth
window.location.href = 'https://auth.sigmaidentity.com/api/oauth/authorize?...';
// 2. After OAuth redirect, connect wallet
const authToken = await generateAuthToken(); // From bitcoin-auth
await fetch('https://auth.sigmaidentity.com/api/wallet/connect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include', // Include session cookie
body: JSON.stringify({ authToken })
});
// 3. Verify NFT ownership
const { owns, count } = await fetch('https://auth.sigmaidentity.com/api/wallet/verify-ownership', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
origin: 'collection-id',
minCount: 50
})
}).then(r => r.json());
// 4. Grant access based on ownership
if (owns) {
console.log(`Access granted! User owns ${count} NFTs`);
}Next Steps
- NFT Verification - NFT ownership guide
- Threshold Verification - Threshold checks
- Examples - Integration examples