Overview
The Consultant API provides endpoints for managing client relationships, portfolio analytics, white-label branding, and bulk operations. All endpoints require authentication with a consultant account.
Base URL : https://your-domain.com/apiAuthentication : Session-based (better-auth)Rate Limits : 100 requests/minute per consultant
Authentication
All consultant API endpoints require an active consultant session.
Cookie : better-auth.session_token=<session-token>
Error Responses
// 401 Unauthorized
{
"error" : "Unauthorized"
}
// 403 Forbidden (not a consultant)
{
"error" : "Consultant access required"
}
Client Management
Search Organizations
Search for organizations to invite as clients.
GET /api/organizations/search
TypeScript
cURL
GET /api/organizations/search?q=acme
Query Parameters :
q (required): Search query (minimum 2 characters)
Response (200 OK):
{
"organizations" : [
{
"id" : "org_123" ,
"name" : "Acme Corporation" ,
"slug" : "acme-corp"
}
],
"query" : "acme"
}
Error Responses :
400 Bad Request: Query too short (< 2 characters)
401 Unauthorized: Not authenticated
500 Internal Server Error: Search failed
Send Client Invitation
Invite an organization to become your client.
POST /api/invitations/send
TypeScript
cURL
POST /api/invitations/send
Content-Type : application/json
{
"organizationId" : "org_123" ,
"message" : "Join my consultant portfolio"
}
Request Body :
{
organizationId : string ; // Organization ID to invite
message ?: string ; // Optional invitation message
}
Response (200 OK):
{
"success" : true ,
"data" : {
"id" : "invite_456" ,
"organizationId" : "org_123" ,
"consultantId" : "consultant_789" ,
"status" : "pending" ,
"createdAt" : "2025-01-18T12:00:00Z"
}
}
Error Responses :
400 Bad Request: Invalid request body
401 Unauthorized: Not authenticated
409 Conflict: Client relationship already exists
500 Internal Server Error: Invitation failed
Accept Client Invitation
Accept an invitation to join a consultant’s portfolio (client side).
POST /api/invitations/accept
TypeScript
POST /api/invitations/accept
Content-Type : application/json
{
"invitationId" : "invite_456"
}
Request Body :
{
invitationId : string ; // Invitation ID to accept
}
Response (200 OK):
{
"success" : true ,
"data" : {
"id" : "client_rel_789" ,
"consultantId" : "consultant_789" ,
"organizationId" : "org_123" ,
"status" : "active" ,
"acceptedAt" : "2025-01-18T12:30:00Z"
}
}
White-Label & Theming
Save Theme Configuration
Save custom theme for a client organization.
POST /api/theme/save
TypeScript
POST /api/theme/save
Content-Type : application/json
{
"organizationId" : "org_123" ,
"theme" : {
"light" : {
"primary" : "0.318 0.132 275.0" ,
"background" : "0.980 0.000 0.0"
},
"dark" : {
"primary" : "0.450 0.150 275.0" ,
"background" : "0.150 0.010 255.0"
}
}
}
Request Body :
{
organizationId : string ;
theme : {
light : Record < string , string > ; // 31 OKLCH color values
dark : Record < string , string > ; // 31 OKLCH color values
};
}
Response (200 OK):
{
"success" : true ,
"message" : "Theme saved successfully" ,
"organizationId" : "org_123"
}
Error Responses :
400 Bad Request: Invalid theme format
401 Unauthorized: Not authenticated
403 Forbidden: Not authorized for this organization
500 Internal Server Error: Save failed
Generate Theme with AI
Generate a complete theme using AI from brand colors.
POST /api/theme/generate
TypeScript
POST /api/theme/generate
Content-Type : application/json
{
"primaryColor" : "#0d1594" ,
"secondaryColor" : "#26dbd9" ,
"context" : "Professional healthcare organization"
}
Request Body :
{
primaryColor : string ; // Hex color (e.g., "#0d1594")
secondaryColor ?: string ; // Optional secondary color
accentColor ?: string ; // Optional accent color
context ?: string ; // Industry/brand context for AI
}
Response (200 OK):
{
"success" : true ,
"theme" : {
"light" : {
"primary" : "0.318 0.132 275.0" ,
"secondary" : "0.700 0.100 200.0" ,
"background" : "0.980 0.000 0.0" ,
// ... 28 more colors
},
"dark" : {
"primary" : "0.450 0.150 275.0" ,
"secondary" : "0.750 0.120 200.0" ,
"background" : "0.150 0.010 255.0" ,
// ... 28 more colors
}
}
}
Error Responses :
400 Bad Request: Invalid color format
401 Unauthorized: Not authenticated
500 Internal Server Error: AI generation failed
503 Service Unavailable: OpenAI API unavailable
AI Generation requires OpenAI API key configuration. Contact your administrator if this endpoint returns 503.
Bulk Operations
Bulk Invite Clients
Invite multiple clients via CSV upload.
POST /api/bulk/invite
TypeScript
POST /api/bulk/invite
Content-Type : multipart/form-data
--boundary
Content-Disposition : form-data; name="file"; filename="clients.csv"
Content-Type : text/csv
organizationId,message
org_123,Join my portfolio
org_456,Let's work together
--boundary--
CSV Format :
organizationId, message
org_123, Join my portfolio
org_456, Let's work together
org_789, Become my client
Response (200 OK):
{
"success" : true ,
"invited" : 3 ,
"failed" : 0 ,
"results" : [
{
"organizationId" : "org_123" ,
"status" : "success" ,
"invitationId" : "invite_001"
},
{
"organizationId" : "org_456" ,
"status" : "success" ,
"invitationId" : "invite_002"
},
{
"organizationId" : "org_789" ,
"status" : "success" ,
"invitationId" : "invite_003"
}
]
}
Error Responses :
400 Bad Request: Invalid CSV format
401 Unauthorized: Not authenticated
413 Payload Too Large: CSV exceeds 1MB
500 Internal Server Error: Processing failed
CSV Limits : Maximum 500 rows per upload, 1MB file size
Export Portfolio Data
Export portfolio analytics to CSV.
GET /api/bulk/export
TypeScript
GET /api/bulk/export?format=csv&period=month
Query Parameters :
format (required): Export format (csv or json)
period (optional): Time period (week, month, quarter, year)
Response (200 OK):
Client, Kindness Acts, Volunteer Hours, Donations, Compliance Rate
Acme Corp, 234, 1200, 45000, 92
TechStart, 45, 300, 12000, 67
GreenCo, 67, 450, 18000, 73
Error Responses :
400 Bad Request: Invalid format
401 Unauthorized: Not authenticated
500 Internal Server Error: Export failed
Health Check
API Health Status
Check API availability and consultant service status.
GET /api/health
TypeScript
Response (200 OK):
{
"status" : "ok" ,
"timestamp" : "2025-01-18T12:00:00Z" ,
"service" : "consultant-api" ,
"version" : "1.0.0"
}
Rate Limiting
All consultant API endpoints are rate-limited to prevent abuse.
Limits :
Authenticated : 100 requests / minute
Bulk operations : 10 requests / minute
AI generation : 5 requests / minute
Rate Limit Headers :
X-RateLimit-Limit : 100
X-RateLimit-Remaining : 95
X-RateLimit-Reset : 1642514400
429 Too Many Requests :
{
"error" : "Rate limit exceeded" ,
"retryAfter" : 60
}
Best Practice : Implement exponential backoff when you receive 429 responses.
Error Handling
All API errors follow a consistent JSON format:
{
"error" : "Human-readable error message" ,
"details" : {
// Optional detailed error information
},
"code" : "ERROR_CODE"
}
Common HTTP Status Codes
Code Meaning Description 200 OK Request succeeded 400 Bad Request Invalid request body or parameters 401 Unauthorized Not authenticated (missing session) 403 Forbidden Authenticated but not authorized 404 Not Found Resource doesn’t exist 409 Conflict Resource already exists 413 Payload Too Large Request body exceeds limits 429 Too Many Requests Rate limit exceeded 500 Internal Server Error Server-side error 503 Service Unavailable Temporary service outage
SDK Integration
TypeScript SDK (Recommended)
Use the @repo/consultant package for type-safe API calls:
import { inviteClient , getPortfolioOverview } from '@repo/consultant' ;
// Invite a client
const invitation = await inviteClient ( consultantId , {
organizationId: 'org_123' ,
message: 'Join my portfolio' ,
});
// Get portfolio analytics
const overview = await getPortfolioOverview ( consultantId );
JavaScript Fetch
For external integrations without the SDK:
async function inviteClient ( organizationId , message ) {
const response = await fetch ( '/api/invitations/send' , {
method: 'POST' ,
credentials: 'include' ,
headers: {
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
organizationId ,
message ,
}),
});
if ( ! response . ok ) {
const error = await response . json ();
throw new Error ( error . error );
}
return response . json ();
}
Webhooks (Coming Soon)
Planned Feature : Webhook support for real-time client events will be available in Phase 2.
Planned webhook events:
client.invited - Client invitation sent
client.accepted - Client accepted invitation
client.cancelled - Client relationship ended
theme.updated - Client theme changed
portfolio.milestone - Portfolio metrics threshold reached
Additional Resources
Consultant Guide User guide for consultant features