Technical Integration, API Development & Blockchain Implementation
This comprehensive technical path covers API integration, blockchain development, smart contract interaction, and custom application development with the CAU Checking App platform.
Prerequisites: Basic programming knowledge (JavaScript, Python, or similar) Estimated Time: 6-8 hours total Target Audience: Software developers, blockchain developers, system integrators Outcome: Full-stack integration capabilities with production-ready code examples
Duration: 60 minutes
The CAU Checking App exposes a comprehensive REST API with the following structure:
https://api.contracting.app/v1/
├── /auth/ # Authentication endpoints
├── /projects/ # Project management
├── /contracts/ # Smart contract operations
├── /payments/ # Payment processing
├── /users/ # User management
├── /organizations/ # Organization endpoints
├── /documents/ # File and document handling
├── /messaging/ # Communication systems
└── /blockchain/ # Blockchain operations
Three authentication methods available:
# Get your API key from Settings > API Keys
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://api.contracting.app/v1/projects
// OAuth flow example
const oauth = {
client_id: 'your_client_id',
redirect_uri: 'https://yourapp.com/callback',
scope: 'read:projects write:payments',
response_type: 'code'
};
window.location.href = `https://contracting.app/oauth/authorize?${new URLSearchParams(oauth)}`;
// Login and get JWT
const response = await fetch('https://api.contracting.app/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'your@email.com',
password: 'your_password'
})
});
const { access_token } = await response.json();
localStorage.setItem('token', access_token);
Local development configuration:
# Clone starter template
git clone https://github.com/cau-checking/api-starter-kit.git
cd api-starter-kit
# Install dependencies
npm install # or pip install -r requirements.txt for Python
# Environment configuration
cp .env.example .env
.env configuration:
# CAU Checking App API Configuration
API_BASE_URL=https://api.contracting.app/v1
API_KEY=your_api_key_here
CLIENT_ID=your_oauth_client_id
CLIENT_SECRET=your_oauth_client_secret
# Development settings
DEBUG=true
LOG_LEVEL=debug
RATE_LIMIT_REQUESTS=1000
Practice Exercise: Set up authentication and make your first API call to retrieve user profile information.
Duration: 90 minutes
Complete project lifecycle through API:
// Create a new project
async function createProject() {
const projectData = {
name: "Downtown Office Complex",
description: "50-story commercial building",
type: "commercial",
budget: 25000000,
phases: [
{ name: "Foundation", percentage: 25, duration_weeks: 8 },
{ name: "Structure", percentage: 35, duration_weeks: 16 },
{ name: "Systems", percentage: 25, duration_weeks: 12 },
{ name: "Finishing", percentage: 15, duration_weeks: 10 }
],
team_members: [
{ email: "pm@contractor.com", role: "project_manager" },
{ email: "super@contractor.com", role: "supervisor" }
]
};
const response = await fetch(`${API_BASE_URL}/projects`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(projectData)
});
const project = await response.json();
console.log('Project created:', project);
return project;
}
// Update project progress
async function updateProjectProgress(projectId, phaseId, completionPercent) {
const update = {
phase_id: phaseId,
completion_percentage: completionPercent,
notes: "Progress update via API",
updated_by: "system"
};
const response = await fetch(`${API_BASE_URL}/projects/${projectId}/progress`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(update)
});
return response.json();
}
Handle payment workflows programmatically:
// Process electronic check payment
async function processECheckPayment(paymentData) {
const payment = {
recipient: {
name: "ABC Subcontractors",
account_number: "123456789",
routing_number: "021000021",
account_type: "checking"
},
amount: 15000.00,
currency: "USD",
description: "Foundation completion payment",
project_id: "proj_123456",
phase_id: "phase_001",
payment_date: "2024-01-15"
};
const response = await fetch(`${API_BASE_URL}/payments/echeck`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payment)
});
return response.json();
}
// Track payment status
async function trackPaymentStatus(paymentId) {
const response = await fetch(`${API_BASE_URL}/payments/${paymentId}/status`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
const status = await response.json();
console.log('Payment Status:', status);
// Status can be: scheduled, processing, completed, failed, cancelled
return status;
}
Handle file uploads and document management:
// Upload project document
async function uploadProjectDocument(projectId, file, documentType) {
const formData = new FormData();
formData.append('file', file);
formData.append('document_type', documentType);
formData.append('project_id', projectId);
const response = await fetch(`${API_BASE_URL}/documents/upload`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`
// Note: Don't set Content-Type for FormData
},
body: formData
});
return response.json();
}
// Get project documents
async function getProjectDocuments(projectId, filters = {}) {
const params = new URLSearchParams({
project_id: projectId,
...filters
});
const response = await fetch(`${API_BASE_URL}/documents?${params}`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
return response.json();
}
Set up real-time event notifications:
// Configure webhooks for project events
async function setupWebhooks() {
const webhookConfig = {
url: "https://yourapp.com/webhooks/cau-checking",
events: [
"project.updated",
"payment.completed",
"payment.failed",
"contract.signed",
"document.uploaded"
],
secret: "your_webhook_secret_key"
};
const response = await fetch(`${API_BASE_URL}/webhooks`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(webhookConfig)
});
return response.json();
}
// Webhook handler example (Express.js)
app.post('/webhooks/cau-checking', (req, res) => {
const signature = req.headers['x-cau-signature'];
const payload = JSON.stringify(req.body);
// Verify webhook signature
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
if (signature === expectedSignature) {
handleWebhookEvent(req.body);
res.status(200).send('OK');
} else {
res.status(401).send('Unauthorized');
}
});
Integration Exercise: Build a simple project dashboard that displays real-time project updates using webhooks.
Duration: 120 minutes
Connect to blockchain networks:
// Web3 initialization
import Web3 from 'web3';
class BlockchainManager {
constructor() {
this.web3 = null;
this.account = null;
this.networkId = null;
}
async connect() {
if (typeof window.ethereum !== 'undefined') {
this.web3 = new Web3(window.ethereum);
try {
// Request account access
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
this.account = accounts[0];
this.networkId = await this.web3.eth.net.getId();
console.log('Connected to:', this.account);
return true;
} catch (error) {
console.error('User denied account access');
return false;
}
} else {
console.error('MetaMask not detected');
return false;
}
}
async switchNetwork(networkId) {
const networks = {
1: { // Ethereum Mainnet
chainId: '0x1',
chainName: 'Ethereum Mainnet',
rpcUrls: ['https://mainnet.infura.io/v3/YOUR_PROJECT_ID']
},
1329: { // SEI Network
chainId: '0x531',
chainName: 'SEI Network',
rpcUrls: ['https://evm-rpc.sei-apis.com'],
nativeCurrency: {
name: 'SEI',
symbol: 'SEI',
decimals: 18
}
}
};
const network = networks[networkId];
if (!network) throw new Error('Unsupported network');
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: network.chainId }]
});
} catch (switchError) {
// Add network if not exists
if (switchError.code === 4902) {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [network]
});
}
}
}
}
Deploy and interact with construction contracts:
// Construction Contract ABI (simplified)
const CONSTRUCTION_CONTRACT_ABI = [
{
"inputs": [
{"name": "projectName", "type": "string"},
{"name": "totalValue", "type": "uint256"},
{"name": "phases", "type": "uint256[]"},
{"name": "participants", "type": "address[]"}
],
"name": "createProject",
"outputs": [{"name": "projectId", "type": "uint256"}],
"type": "function"
},
{
"inputs": [
{"name": "projectId", "type": "uint256"},
{"name": "phaseIndex", "type": "uint256"}
],
"name": "approvePhase",
"outputs": [],
"type": "function"
},
{
"inputs": [
{"name": "projectId", "type": "uint256"},
{"name": "phaseIndex", "type": "uint256"},
{"name": "amount", "type": "uint256"}
],
"name": "releasePayment",
"outputs": [],
"type": "function"
}
];
class SmartContractManager {
constructor(web3, contractAddress) {
this.web3 = web3;
this.contract = new web3.eth.Contract(CONSTRUCTION_CONTRACT_ABI, contractAddress);
}
async createProject(projectData) {
const { name, totalValue, phases, participants } = projectData;
try {
const gasEstimate = await this.contract.methods
.createProject(name, totalValue, phases, participants)
.estimateGas({ from: this.web3.eth.defaultAccount });
const result = await this.contract.methods
.createProject(name, totalValue, phases, participants)
.send({
from: this.web3.eth.defaultAccount,
gas: gasEstimate,
gasPrice: await this.web3.eth.getGasPrice()
});
console.log('Project created:', result);
return result;
} catch (error) {
console.error('Error creating project:', error);
throw error;
}
}
async approvePhase(projectId, phaseIndex) {
try {
const result = await this.contract.methods
.approvePhase(projectId, phaseIndex)
.send({ from: this.web3.eth.defaultAccount });
console.log('Phase approved:', result);
return result;
} catch (error) {
console.error('Error approving phase:', error);
throw error;
}
}
// Listen for contract events
setupEventListeners() {
this.contract.events.ProjectCreated({
filter: {},
fromBlock: 'latest'
})
.on('data', (event) => {
console.log('New project created:', event);
this.handleProjectCreated(event);
})
.on('error', console.error);
this.contract.events.PhaseApproved({
filter: {},
fromBlock: 'latest'
})
.on('data', (event) => {
console.log('Phase approved:', event);
this.handlePhaseApproved(event);
});
}
handleProjectCreated(event) {
// Sync with API backend
const projectData = {
blockchain_id: event.returnValues.projectId,
transaction_hash: event.transactionHash,
block_number: event.blockNumber
};
fetch(`${API_BASE_URL}/projects/sync-blockchain`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(projectData)
});
}
}
Use the contract factory for standardized deployments:
class ContractFactoryManager {
constructor(web3, factoryAddress) {
this.web3 = web3;
this.factoryAddress = factoryAddress;
this.factoryContract = new web3.eth.Contract(FACTORY_ABI, factoryAddress);
}
async deployProjectContract(projectConfig) {
const {
projectName,
description,
totalValue,
participants,
phasePercentages,
ipfsHash
} = projectConfig;
try {
// Estimate gas for deployment
const gasEstimate = await this.factoryContract.methods
.createProject(
projectName,
description,
this.web3.utils.toWei(totalValue.toString(), 'ether'),
participants,
phasePercentages,
ipfsHash || ""
)
.estimateGas({ from: this.web3.eth.defaultAccount });
// Deploy contract
const result = await this.factoryContract.methods
.createProject(
projectName,
description,
this.web3.utils.toWei(totalValue.toString(), 'ether'),
participants,
phasePercentages,
ipfsHash || ""
)
.send({
from: this.web3.eth.defaultAccount,
gas: Math.floor(gasEstimate * 1.2), // Add 20% buffer
gasPrice: await this.web3.eth.getGasPrice()
});
console.log('Contract deployed:', result);
return {
transactionHash: result.transactionHash,
contractAddress: result.events.ProjectCreated.returnValues.contractAddress,
projectId: result.events.ProjectCreated.returnValues.projectId
};
} catch (error) {
console.error('Deployment failed:', error);
throw error;
}
}
async getProjectContract(projectId) {
const contractAddress = await this.factoryContract.methods
.getProject(projectId)
.call();
return new this.web3.eth.Contract(CONSTRUCTION_CONTRACT_ABI, contractAddress);
}
}
Blockchain Exercise: Deploy a test contract for a sample construction project and interact with it through both the smart contract and API.
Duration: 75 minutes
Set up real-time communication:
class RealtimeManager {
constructor(apiKey) {
this.apiKey = apiKey;
this.socket = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}
connect() {
const wsUrl = `wss://ws.contracting.app/v1?token=${this.apiKey}`;
this.socket = new WebSocket(wsUrl);
this.socket.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.subscribe(['projects', 'payments', 'contracts']);
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
this.socket.onclose = () => {
console.log('WebSocket disconnected');
this.attemptReconnect();
};
this.socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
subscribe(channels) {
const subscription = {
type: 'subscribe',
channels: channels
};
this.send(subscription);
}
send(data) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(data));
}
}
handleMessage(data) {
switch (data.type) {
case 'project_updated':
this.onProjectUpdate(data.payload);
break;
case 'payment_completed':
this.onPaymentComplete(data.payload);
break;
case 'contract_signed':
this.onContractSigned(data.payload);
break;
default:
console.log('Unknown message type:', data.type);
}
}
onProjectUpdate(project) {
// Update local state or trigger UI updates
console.log('Project updated:', project);
document.dispatchEvent(new CustomEvent('projectUpdate', {
detail: project
}));
}
attemptReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.pow(2, this.reconnectAttempts) * 1000; // Exponential backoff
setTimeout(() => {
console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
this.connect();
}, delay);
}
}
}
Implement robust error handling:
class APIClient {
constructor(baseURL, apiKey) {
this.baseURL = baseURL;
this.apiKey = apiKey;
this.maxRetries = 3;
this.retryDelay = 1000;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const defaultOptions = {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
...options.headers
}
};
const requestOptions = { ...defaultOptions, ...options };
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
const response = await fetch(url, requestOptions);
if (response.ok) {
return await response.json();
}
// Handle specific HTTP errors
if (response.status === 429) {
// Rate limited - wait and retry
const retryAfter = response.headers.get('Retry-After') || 60;
await this.sleep(retryAfter * 1000);
continue;
}
if (response.status >= 500 && attempt < this.maxRetries) {
// Server error - retry with exponential backoff
await this.sleep(this.retryDelay * Math.pow(2, attempt));
continue;
}
// Client error or max retries exceeded
const errorData = await response.json();
throw new APIError(response.status, errorData.message, errorData);
} catch (error) {
if (attempt === this.maxRetries) {
throw error;
}
// Network error - retry
await this.sleep(this.retryDelay * Math.pow(2, attempt));
}
}
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Convenience methods
async get(endpoint) {
return this.request(endpoint, { method: 'GET' });
}
async post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
}
async put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
}
async delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
}
class APIError extends Error {
constructor(status, message, data = {}) {
super(message);
this.name = 'APIError';
this.status = status;
this.data = data;
}
}
Handle large datasets efficiently:
class BatchOperationManager {
constructor(apiClient) {
this.apiClient = apiClient;
this.batchSize = 50;
}
async batchCreateProjects(projects) {
const batches = this.createBatches(projects, this.batchSize);
const results = [];
for (let i = 0; i < batches.length; i++) {
console.log(`Processing batch ${i + 1}/${batches.length}`);
try {
const batchResult = await this.apiClient.post('/projects/batch', {
projects: batches[i]
});
results.push(...batchResult.created);
} catch (error) {
console.error(`Batch ${i + 1} failed:`, error);
// Fallback: Process individually
for (const project of batches[i]) {
try {
const individual = await this.apiClient.post('/projects', project);
results.push(individual);
} catch (individualError) {
console.error('Individual project creation failed:', individualError);
}
}
}
// Rate limiting protection
if (i < batches.length - 1) {
await this.sleep(500);
}
}
return results;
}
async batchUpdatePayments(updates) {
// Process updates in parallel with concurrency limit
const concurrencyLimit = 5;
const results = [];
for (let i = 0; i < updates.length; i += concurrencyLimit) {
const batch = updates.slice(i, i + concurrencyLimit);
const promises = batch.map(async (update) => {
try {
return await this.apiClient.put(`/payments/${update.id}`, update.data);
} catch (error) {
console.error(`Payment update failed for ${update.id}:`, error);
return { error: error.message, id: update.id };
}
});
const batchResults = await Promise.all(promises);
results.push(...batchResults);
}
return results;
}
createBatches(array, batchSize) {
const batches = [];
for (let i = 0; i < array.length; i += batchSize) {
batches.push(array.slice(i, i + batchSize));
}
return batches;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Performance Exercise: Build a data synchronization system that efficiently handles large datasets and network issues.
Duration: 60 minutes
Configure for production deployment:
// config/production.js
module.exports = {
api: {
baseURL: process.env.CAU_API_BASE_URL || 'https://api.contracting.app/v1',
key: process.env.CAU_API_KEY,
timeout: 30000,
retries: 3
},
blockchain: {
networkId: process.env.NETWORK_ID || 1329, // SEI Mainnet
rpcUrl: process.env.RPC_URL,
contractAddress: process.env.CONTRACT_ADDRESS
},
logging: {
level: process.env.LOG_LEVEL || 'info',
file: process.env.LOG_FILE || '/var/log/cau-integration.log'
},
monitoring: {
enabled: true,
errorTracking: process.env.SENTRY_DSN,
metrics: process.env.METRICS_ENDPOINT
}
};
Implement structured logging:
const winston = require('winston');
class Logger {
constructor() {
this.logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'cau-integration' },
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' }),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
}
logAPICall(endpoint, method, duration, statusCode) {
this.logger.info('API Call', {
endpoint,
method,
duration,
statusCode,
timestamp: new Date().toISOString()
});
}
logBlockchainTransaction(txHash, operation, gasUsed) {
this.logger.info('Blockchain Transaction', {
txHash,
operation,
gasUsed,
timestamp: new Date().toISOString()
});
}
logError(error, context = {}) {
this.logger.error('Application Error', {
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString()
});
}
}
// Usage example
const logger = new Logger();
// In API client
class MonitoredAPIClient extends APIClient {
async request(endpoint, options = {}) {
const startTime = Date.now();
try {
const result = await super.request(endpoint, options);
const duration = Date.now() - startTime;
logger.logAPICall(endpoint, options.method || 'GET', duration, 200);
return result;
} catch (error) {
const duration = Date.now() - startTime;
logger.logAPICall(endpoint, options.method || 'GET', duration, error.status || 0);
logger.logError(error, { endpoint, options });
throw error;
}
}
}
Set up comprehensive health checks:
class HealthMonitor {
constructor(apiClient, blockchainManager) {
this.apiClient = apiClient;
this.blockchainManager = blockchainManager;
this.metrics = {
apiResponseTimes: [],
blockchainConnections: 0,
errors: 0,
uptime: process.uptime()
};
}
async checkHealth() {
const checks = await Promise.allSettled([
this.checkAPIHealth(),
this.checkBlockchainHealth(),
this.checkDatabaseHealth()
]);
const results = {
timestamp: new Date().toISOString(),
overall: 'healthy',
services: {
api: checks[0].status === 'fulfilled' ? checks[0].value : { status: 'unhealthy', error: checks[0].reason },
blockchain: checks[1].status === 'fulfilled' ? checks[1].value : { status: 'unhealthy', error: checks[1].reason },
database: checks[2].status === 'fulfilled' ? checks[2].value : { status: 'unhealthy', error: checks[2].reason }
},
metrics: this.getMetrics()
};
// Determine overall health
const unhealthyServices = Object.values(results.services)
.filter(service => service.status === 'unhealthy');
if (unhealthyServices.length > 0) {
results.overall = 'unhealthy';
this.sendAlert(results);
}
return results;
}
async checkAPIHealth() {
const start = Date.now();
try {
await this.apiClient.get('/health');
const responseTime = Date.now() - start;
this.metrics.apiResponseTimes.push(responseTime);
if (this.metrics.apiResponseTimes.length > 100) {
this.metrics.apiResponseTimes.shift();
}
return {
status: 'healthy',
responseTime,
averageResponseTime: this.getAverageResponseTime()
};
} catch (error) {
this.metrics.errors++;
return {
status: 'unhealthy',
error: error.message,
lastError: new Date().toISOString()
};
}
}
async checkBlockchainHealth() {
try {
if (!this.blockchainManager.web3) {
return { status: 'unhealthy', error: 'Web3 not initialized' };
}
const blockNumber = await this.blockchainManager.web3.eth.getBlockNumber();
const networkId = await this.blockchainManager.web3.eth.net.getId();
return {
status: 'healthy',
blockNumber,
networkId,
connected: true
};
} catch (error) {
return {
status: 'unhealthy',
error: error.message,
connected: false
};
}
}
async checkDatabaseHealth() {
// Implement database health check based on your setup
return { status: 'healthy', connections: 'active' };
}
getAverageResponseTime() {
if (this.metrics.apiResponseTimes.length === 0) return 0;
return this.metrics.apiResponseTimes.reduce((a, b) => a + b, 0) / this.metrics.apiResponseTimes.length;
}
getMetrics() {
return {
uptime: process.uptime(),
memoryUsage: process.memoryUsage(),
errors: this.metrics.errors,
averageApiResponseTime: this.getAverageResponseTime()
};
}
async sendAlert(healthResults) {
// Implement alerting (email, Slack, PagerDuty, etc.)
console.log('ALERT: System unhealthy', healthResults);
// Example: Send to monitoring service
try {
await fetch(process.env.ALERT_WEBHOOK, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `CAU Integration Health Alert: ${healthResults.overall}`,
details: healthResults
})
});
} catch (error) {
console.error('Failed to send alert:', error);
}
}
startPeriodicHealthChecks(intervalMinutes = 5) {
setInterval(async () => {
try {
const health = await this.checkHealth();
console.log('Health Check:', health.overall);
} catch (error) {
console.error('Health check failed:', error);
}
}, intervalMinutes * 60 * 1000);
}
}
Production Exercise: Set up a complete monitoring system with health checks, alerting, and performance tracking.
You've completed the Developer Learning Path and now have comprehensive skills for production-grade integration with the CAU Checking App platform.
# JavaScript/Node.js SDK
npm install @cau-checking/sdk
# Python SDK
pip install cau-checking-python
# Go SDK
go get github.com/cau-checking/go-sdk
# PHP Composer
composer require cau-checking/php-sdk
Ready to build the future of construction technology? Join our Developer Community and start contributing!