Skip to main content

Profile API

The Profile API provides standardized endpoints for managing user profiles, builder profiles, and related data with full type safety and consistent response formats.

Core Features

  • Standardized Responses: All endpoints follow the StandardApiResponse pattern
  • Type Safety: Full TypeScript interfaces for all requests and responses
  • Authentication Integration: Built-in permission handling and ownership verification
  • Error Handling: Comprehensive error codes and messages
  • Component Compatibility: Flattened response structures for easy UI integration

Response Structure

All Profile API endpoints return the standardized response format:
interface StandardApiResponse<T> {
  success: boolean;
  data?: T;
  error?: ApiError;
  message?: string;
}

BuilderProfileResponse

The core profile response includes flattened user and profile data for component compatibility:
interface BuilderProfileResponseData {
  // User information
  userId: string;
  clerkId?: string;
  email: string;
  name?: string;
  
  // Profile information (flattened)
  id: string;
  bio?: string;
  headline?: string;
  slug?: string;
  tagline?: string;
  displayName?: string;
  title?: string;
  validationTier: number;
  domains: string[];
  badges: string[];
  completedProjects: number;
  responseRate?: number;
  hourlyRate?: number;
  availableForHire: boolean;
  adhd_focus: boolean;
  expertiseAreas: Record<string, any>;
  socialLinks: Record<string, string>;
  portfolioItems: any[];
  featured: boolean;
  searchable: boolean;
  availability: string;
  topSkills: string[];
  
  // Component compatibility properties
  avatarUrl?: string;
  coverImageUrl?: string;
  joinDate?: Date;
  rating?: number;
  avatar?: { url: string; alt?: string };
  specializations?: string[];
  
  // Related data
  sessionTypes?: SessionTypeWithId[];
  permissions?: ProfilePermissions;
}

type BuilderProfileResponse = StandardApiResponse<BuilderProfileResponseData>;

API Endpoints

Get Builder Profile by ID

GET
/api/profiles/builder/[id]
Retrieve a builder profile by its unique identifier
GET /api/profiles/builder/[id]

Get Builder Profile by Slug

GET
/api/profiles/builder/slug/[slug]
Retrieve a builder profile by its unique slug identifier
GET /api/profiles/builder/slug/[slug]

Client-Side API Functions

getBuilderProfileById()

import { getBuilderProfileById } from '@/lib/profile/api';

async function fetchProfile(id: string) {
  const response = await getBuilderProfileById(id);
  
  if (!response.success || !response.data) {
    console.error('Profile fetch failed:', response.error?.message);
    return null;
  }
  
  return response.data;
}

getBuilderProfileBySlug()

import { getBuilderProfileBySlug } from '@/lib/profile/api';

async function fetchProfileBySlug(slug: string) {
  const response = await getBuilderProfileBySlug(slug);
  
  if (!response.success || !response.data) {
    console.error('Profile fetch failed:', response.error?.message);
    return null;
  }
  
  return response.data;
}

Component Integration

React Server Component

import { getBuilderProfileBySlug } from '@/lib/profile/api';
import { notFound } from 'next/navigation';

export default async function BuilderProfilePage({ 
  params 
}: { 
  params: { slug: string } 
}) {
  const profileResponse = await getBuilderProfileBySlug(params.slug);
  
  if (!profileResponse.success || !profileResponse.data) {
    notFound();
  }
  
  const profile = profileResponse.data;
  
  return (
    <div className="profile-container">
      <div className="profile-header">
        {profile.avatar && (
          <img 
            src={profile.avatar.url} 
            alt={profile.displayName || 'Profile'} 
            className="avatar"
          />
        )}
        <h1>{profile.displayName}</h1>
        <p>{profile.headline}</p>
      </div>
      
      <div className="profile-content">
        <p>{profile.bio}</p>
        
        <div className="skills">
          <h3>Top Skills</h3>
          <ul>
            {profile.topSkills.map(skill => (
              <li key={skill}>{skill}</li>
            ))}
          </ul>
        </div>
        
        {profile.specializations && (
          <div className="specializations">
            <h3>Specializations</h3>
            <ul>
              {profile.specializations.map(spec => (
                <li key={spec}>{spec}</li>
              ))}
            </ul>
          </div>
        )}
      </div>
    </div>
  );
}

Client Component with Error Handling

'use client';

import { useState, useEffect } from 'react';
import { getBuilderProfileById } from '@/lib/profile/api';
import type { BuilderProfileResponseData } from '@/lib/profile/types';

interface ProfileViewProps {
  profileId: string;
}

export function ProfileView({ profileId }: ProfileViewProps) {
  const [profile, setProfile] = useState<BuilderProfileResponseData | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  
  useEffect(() => {
    async function loadProfile() {
      try {
        const response = await getBuilderProfileById(profileId);
        
        if (!response.success) {
          setError(response.error?.message || 'Failed to load profile');
          return;
        }
        
        setProfile(response.data);
      } catch (err) {
        setError('An unexpected error occurred');
      } finally {
        setLoading(false);
      }
    }
    
    loadProfile();
  }, [profileId]);
  
  if (loading) {
    return <div className="loading">Loading profile...</div>;
  }
  
  if (error) {
    return (
      <div className="error-container">
        <h2>Error Loading Profile</h2>
        <p>{error}</p>
        <button onClick={() => window.location.reload()}>
          Try Again
        </button>
      </div>
    );
  }
  
  if (!profile) {
    return <div className="not-found">Profile not found</div>;
  }
  
  return (
    <div className="profile-view">
      <h1>{profile.displayName}</h1>
      <p>{profile.bio}</p>
      
      {profile.permissions?.canEdit && (
        <button className="edit-button">
          Edit Profile
        </button>
      )}
    </div>
  );
}

Type Definitions

Core Profile Types

// From lib/profile/types.ts
export interface BuilderProfileData {
  id: string;
  bio?: string;
  headline?: string;
  slug?: string;
  tagline?: string;
  displayName?: string;
  validationTier: number;
  domains: string[];
  badges: string[];
  completedProjects: number;
  responseRate?: number;
  hourlyRate?: number;
  availableForHire: boolean;
  adhd_focus: boolean;
  expertiseAreas: Record<string, any>;
  socialLinks: Record<string, string>;
  portfolioItems: any[];
  featured: boolean;
  searchable: boolean;
  availability: string;
  topSkills: string[];
  avatarUrl?: string;
  coverImageUrl?: string;
  joinDate?: Date;
  rating?: number;
}

export interface ProfilePermissions {
  canView: boolean;
  canEdit: boolean;
  canDelete: boolean;
  canVerify: boolean;
}

export interface SessionTypeWithId {
  id: string;
  title: string;
  description: string;
  durationMinutes: number;
  price: number;
  currency: string;
  isActive: boolean;
  color?: string | null;
  maxParticipants?: number;
}

Database Integration

Profile Data Mapping

The API automatically maps database fields to component-compatible formats:
// Database → API Response Mapping
{
  // User fields
  userId: builderProfile.user.id,
  clerkId: builderProfile.user.clerkId,
  email: builderProfile.user.email,
  name: builderProfile.user.name,
  
  // Profile fields (flattened)
  id: builderProfile.id,
  bio: builderProfile.bio,
  headline: builderProfile.headline,
  title: builderProfile.headline, // Mapped for compatibility
  
  // Skills transformation
  topSkills: formattedSkills.map(skill => skill.name),
  
  // Image mapping  
  avatarUrl: builderProfile.user.imageUrl,
  avatar: builderProfile.user.imageUrl ? { 
    url: builderProfile.user.imageUrl 
  } : undefined,
  
  // Domain mapping
  specializations: builderProfile.domains || [],
  
  // Timestamp mapping
  joinDate: builderProfile.createdAt,
}

Null/Undefined Normalization

The API handles database null values consistently:
// Automatic null → undefined conversion
domains: builderProfile.domains || [],
badges: builderProfile.badges || [],
expertiseAreas: builderProfile.expertiseAreas || {},
socialLinks: builderProfile.socialLinks || {},
portfolioItems: builderProfile.portfolioItems || [],

// Conditional properties
rating: undefined, // Not available from current schema
coverImageUrl: undefined, // Not available from current schema

Testing

Unit Tests

import { getBuilderProfileById } from '@/lib/profile/api';

describe('Profile API', () => {
  it('should return standardized response format', async () => {
    const response = await getBuilderProfileById('valid_id');
    
    expect(response).toHaveProperty('success');
    expect(response).toHaveProperty('data');
    expect(typeof response.success).toBe('boolean');
  });
  
  it('should handle invalid profile ID', async () => {
    const response = await getBuilderProfileById('invalid_id');
    
    expect(response.success).toBe(false);
    expect(response.error).toBeDefined();
    expect(response.error?.code).toBe('not_found');
  });
});

Integration Tests

describe('Profile API Integration', () => {
  it('should return complete profile data', async () => {
    const response = await fetch('/api/profiles/builder/test_id');
    const data = await response.json();
    
    expect(data.success).toBe(true);
    expect(data.data).toHaveProperty('userId');
    expect(data.data).toHaveProperty('displayName');
    expect(data.data).toHaveProperty('permissions');
  });
});

Migration from Legacy APIs

Before (Legacy Format)

// Old response format
{
  id: "profile_123",
  user: {
    id: "user_456",
    name: "John Doe"
  },
  bio: "Builder bio",
  skills: ["React", "TypeScript"]
}

After (Standardized Format)

// New standardized format
{
  success: true,
  data: {
    userId: "user_456",
    id: "profile_123", 
    name: "John Doe",
    bio: "Builder bio",
    topSkills: ["React", "TypeScript"],
    permissions: {
      canView: true,
      canEdit: false,
      canDelete: false,
      canVerify: false
    }
  }
}
The Profile API provides a robust, type-safe foundation for all profile-related operations in the BuildAppsWith platform, ensuring consistent data access patterns and reliable error handling across all client applications.