Opportunities API
Sales pipeline and CRM for tracking leads and converting prospects to clients.
Overview
Opportunities (sales leads) help you:
- Track potential clients before they book
- Manage a sales pipeline with stages
- Log interactions and follow-ups
- Measure conversion rates
- Convert prospects to full client records
Data Models
Opportunity
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
client_id | UUID | Linked client (if converted) |
prospect_name | String | Prospect's name (if not a client) |
prospect_email | String | Prospect's email |
prospect_phone | String | Prospect's phone |
category_id | UUID | Opportunity category |
title | String | Brief description |
description | String | Detailed notes |
estimated_value | Decimal | Expected revenue |
probability | Integer | Win probability (0-100%) |
status | Enum | Pipeline stage |
source | String | Lead source |
assigned_to | UUID | Assigned staff member |
follow_up_date | DateTime | Next follow-up date |
closed_at | DateTime | When won/lost |
closed_reason | String | Why closed |
created_by | UUID | Who created |
Status Values (Pipeline Stages)
| Status | Description |
|---|---|
open | New lead, not yet contacted |
contacted | Initial contact made |
qualified | Lead is qualified, interested |
proposal | Proposal/quote sent |
won | Converted to sale |
lost | Did not convert |
stale | No activity, gone cold |
Lead Sources
Common values for source:
website— Website inquiry formreferral— Client referralwalk_in— Walk-in visitorphone— Phone inquirysocial— Social mediaevent— Event or expoother— Other source
Opportunity Category
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
name | String | Category name |
color | String | Display color (hex) |
sort_order | Integer | Display order |
is_active | Boolean | Available for use |
Opportunity Note
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
opportunity_id | UUID | Parent opportunity |
content | String | Note text |
created_by | UUID | Note author |
created_at | DateTime | When written |
Opportunity Activity
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
opportunity_id | UUID | Parent opportunity |
activity_type | Enum | Type of activity |
description | String | Activity details |
outcome | String | Result of activity |
scheduled_at | DateTime | When scheduled |
completed_at | DateTime | When completed |
created_by | UUID | Who logged it |
Activity Types:
call— Phone callemail— Email sentmeeting— In-person meetingnote— General notestatus_change— Status updatedtask— Task completedother— Other activity
Categories
Get Categories
import { getOpportunityCategories } from '@/lib/actions/opportunities'
const result = await getOpportunityCategories()Create Category
import { createOpportunityCategory } from '@/lib/actions/opportunities'
const result = await createOpportunityCategory({
name: 'Botox Inquiry',
color: '#10b981' // emerald
})Opportunities
Get Opportunities
import { getOpportunities } from '@/lib/actions/opportunities'
// All open opportunities
const result = await getOpportunities({
status: ['open', 'contacted', 'qualified', 'proposal']
})
// Assigned to specific staff
const myLeads = await getOpportunities({
assigned_to: 'staff-uuid'
})
// Search
const search = await getOpportunities({
search: 'botox',
limit: 20,
offset: 0
})Response includes:
- Opportunity details
- Linked client (if any)
- Category with color
- Assigned staff
- Notes and activities counts
Get Single Opportunity
import { getOpportunity } from '@/lib/actions/opportunities'
const result = await getOpportunity('opportunity-uuid')
// Includes full notes and activity historyCreate Opportunity
import { createOpportunity } from '@/lib/actions/opportunities'
// New prospect (not yet a client)
const result = await createOpportunity({
title: 'Interested in Botox package',
prospect_name: 'Jane Doe',
prospect_email: 'jane@example.com',
prospect_phone: '+15551234567',
category_id: 'category-uuid',
estimated_value: 500.00,
probability: 50,
source: 'website',
assigned_to: 'staff-uuid',
follow_up_date: '2026-02-20T10:00:00Z',
description: 'Filled out inquiry form for Botox. First-time client.'
})
// Existing client with new opportunity
const existing = await createOpportunity({
title: 'Membership upgrade',
client_id: 'client-uuid',
category_id: 'membership-category-uuid',
estimated_value: 1200.00,
probability: 75,
source: 'referral'
})Update Opportunity
import { updateOpportunity } from '@/lib/actions/opportunities'
const result = await updateOpportunity('opportunity-uuid', {
status: 'qualified',
probability: 70,
estimated_value: 600.00,
follow_up_date: '2026-02-25T14:00:00Z'
})Close Opportunity
import { closeOpportunity } from '@/lib/actions/opportunities'
// Won!
const won = await closeOpportunity('opportunity-uuid', {
won: true,
transaction_id: 'transaction-uuid' // optional: link to sale
})
// Lost
const lost = await closeOpportunity('opportunity-uuid', {
won: false,
reason: 'Went with competitor - price sensitive'
})Delete Opportunity
import { deleteOpportunity } from '@/lib/actions/opportunities'
const result = await deleteOpportunity('opportunity-uuid')Notes
Add Note
import { addOpportunityNote } from '@/lib/actions/opportunities'
const result = await addOpportunityNote(
'opportunity-uuid',
'Called and left voicemail. Will try again tomorrow.'
)Get Notes
import { getOpportunityNotes } from '@/lib/actions/opportunities'
const result = await getOpportunityNotes('opportunity-uuid')
// Returns notes in reverse chronological orderActivities
Log Activity
import { logOpportunityActivity } from '@/lib/actions/opportunities'
// Log a phone call
const result = await logOpportunityActivity({
opportunity_id: 'opportunity-uuid',
activity_type: 'call',
description: 'Discussed treatment options',
outcome: 'Very interested, sending info packet'
})
// Schedule a follow-up meeting
const meeting = await logOpportunityActivity({
opportunity_id: 'opportunity-uuid',
activity_type: 'meeting',
description: 'Consultation appointment',
scheduled_at: '2026-02-20T14:00:00Z'
})
// Complete a scheduled activity
const completed = await logOpportunityActivity({
opportunity_id: 'opportunity-uuid',
activity_type: 'meeting',
description: 'Consultation completed',
outcome: 'Ready to book, sending proposal',
completed_at: new Date().toISOString()
})Get Activities
import { getOpportunityActivities } from '@/lib/actions/opportunities'
const result = await getOpportunityActivities('opportunity-uuid')
// Full activity timelinePipeline Statistics
Get Stats
import { getOpportunityStats } from '@/lib/actions/opportunities'
const result = await getOpportunityStats()Response:
{
"success": true,
"data": {
"counts": {
"open": 15,
"contacted": 8,
"qualified": 5,
"proposal": 3,
"won": 12,
"lost": 7,
"stale": 2
},
"totalValue": 25000.00,
"weightedValue": 12500.00,
"needsFollowUp": 4,
"total": 52
}
}totalValue— Sum of estimated_value for open opportunitiesweightedValue— Value × probability for open opportunitiesneedsFollowUp— Opportunities with follow_up_date ≤ today
Convert to Client
Convert a prospect to a full client record.
import { convertToClient } from '@/lib/actions/opportunities'
const result = await convertToClient('opportunity-uuid')
if (result.success) {
const { client_id } = result.data
// Opportunity is now linked to this client
// Prospect info was used to create client record
}What happens:
- Creates new client from prospect_name, prospect_email, prospect_phone
- Links the opportunity to the new client
- Logs activity for the conversion
Workflows
Website Lead Workflow
// 1. Receive inquiry from website form
const opportunity = await createOpportunity({
title: 'Website Inquiry - Botox',
prospect_name: formData.name,
prospect_email: formData.email,
prospect_phone: formData.phone,
source: 'website',
description: formData.message,
follow_up_date: tomorrow
})
// 2. Assign to staff
await updateOpportunity(opportunity.data.id, {
assigned_to: 'sales-staff-uuid'
})
// 3. Staff makes contact
await logOpportunityActivity({
opportunity_id: opportunity.data.id,
activity_type: 'call',
description: 'Initial outreach call',
outcome: 'Left voicemail'
})
// 4. Follow up, qualify, etc.
// 5. Convert when ready to book
await convertToClient(opportunity.data.id)
await closeOpportunity(opportunity.data.id, { won: true })Membership Expiration Workflow
// When a membership is expiring, create opportunity for renewal
const opportunity = await createOpportunity({
title: 'Membership Renewal',
client_id: existingClientId,
category_id: 'renewal-category-uuid',
estimated_value: membershipPrice,
probability: 70, // Existing clients more likely to renew
source: 'internal',
follow_up_date: thirtyDaysBeforeExpiration
})Best Practices
- Set follow-up dates — Don't let leads go cold
- Log all interactions — Build relationship history
- Use categories — Segment for reporting
- Track sources — Know what marketing works
- Review stale leads — Weekly pipeline review
- Assign owners — Clear accountability
- Convert promptly — Don't duplicate data entry