CRM & Marketing Platform API Reference
Version: 2.0
Base URL: /api/v1
Authentication: Bearer JWT Token
Table of Contents
- Authentication
- Customer Profiles
- Identity Resolution
- Activity Timeline
- Customer Notes
- Segments
- Campaigns
- Journeys
- Social Connections
- Social Posts
- Event Promotions
- Analytics
- GraphQL Schema
- Error Responses
Authentication
All endpoints require authentication via Bearer token.
Authorization: Bearer <jwt_token>
Required Scopes
| Scope | Description |
|---|---|
crm:read | Read profiles, timeline, notes |
crm:write | Create/update profiles, add notes |
crm:admin | Merge profiles, manage segments |
crm:campaigns | Manage campaigns and journeys |
crm:social | Manage social connections and posts |
Customer Profiles
List Customers
GET /customers
Query Parameters
| Parameter | Type | Description |
|---|---|---|
tenantId | string | Tenant ID (required) |
status | string | Filter: ACTIVE, INACTIVE, MERGED, DELETED |
lifecycleStage | string | Filter: SUBSCRIBER, LEAD, CUSTOMER, MEMBER, ADVOCATE, CHURNED |
search | string | Search by name, email, phone |
segmentId | string | Filter by segment membership |
engagementMin | number | Min engagement score (0-100) |
churnRiskMin | number | Min churn risk (0-100) |
emailOptIn | boolean | Filter by opt-in |
skip | number | Offset (default: 0) |
take | number | Limit (default: 25, max: 100) |
Response: 200 OK
{
"data": [{
"id": "uuid",
"email": "john@example.com",
"firstName": "John",
"lastName": "Smith",
"status": "ACTIVE",
"engagementScore": 75,
"churnRiskScore": 15,
"lastActivityAt": "2025-12-14T10:00:00Z"
}],
"meta": { "total": 1250, "skip": 0, "take": 25, "hasMore": true }
}
Get Customer
GET /customers/:id
Create Customer
POST /customers
Update Customer
PATCH /customers/:id
Merge Customers
POST /customers/:winnerId/merge
Request Body
{
"loserId": "uuid-to-merge"
}
Identity Resolution
Resolve Customer
GET /customers/resolve
Query Parameters
| Parameter | Type | Description |
|---|---|---|
tenantId | string | Required |
authUserId | string | IDP user ID |
email | string | |
phone | string | Phone (E.164) |
Response
{
"profile": { /* customer */ },
"resolution": {
"isNew": false,
"matchedBy": ["email"],
"confidence": 95
}
}
Activity Timeline
Get Timeline
GET /customers/:id/activities
Query Parameters
| Parameter | Type | Description |
|---|---|---|
categories | string[] | MEMBERSHIP, BOOKING, COMMUNICATION, ENGAGEMENT, FINANCIAL, GOLF, MARKETING, SOCIAL, SYSTEM |
startDate | string | ISO 8601 date |
endDate | string | ISO 8601 date |
skip | number | Offset |
take | number | Limit |
Add Activity
POST /customers/:id/activities
Customer Notes
List Notes
GET /customers/:id/notes
Create Note
POST /customers/:id/notes
Request Body
{
"content": "Note content",
"type": "GENERAL",
"isPinned": false
}
Update Note
PATCH /notes/:id
Delete Note
DELETE /notes/:id
Segments
List Segments
GET /segments
Query Parameters
| Parameter | Type | Description |
|---|---|---|
tenantId | string | Required |
type | string | STATIC, DYNAMIC, SMART |
isActive | boolean | Filter active |
Get Segment
GET /segments/:id
Get Segment Members
GET /segments/:id/members
Create Segment
POST /segments
Request Body
{
"tenantId": "tenant_123",
"name": "High-Value Members",
"type": "DYNAMIC",
"criteria": {
"combinator": "AND",
"rules": [
{ "field": "engagementScore", "operator": "gte", "value": 70 },
{ "field": "emailOptIn", "operator": "eq", "value": true }
]
}
}
Refresh Segment
POST /segments/:id/refresh
Triggers recalculation of dynamic segment membership.
Sync to Messaging
POST /segments/:id/sync-to-messaging
Syncs segment membership to Messaging service for campaign execution.
Response
{
"messagingSegmentId": "msg_segment_123",
"syncedCount": 342,
"syncedAt": "2025-12-14T10:00:00Z"
}
Campaigns
List Campaigns
GET /campaigns
Query Parameters
| Parameter | Type | Description |
|---|---|---|
tenantId | string | Required |
clubId | string | Filter by club |
status | string | DRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, CANCELLED |
type | string | ONE_TIME, RECURRING, TRIGGERED, JOURNEY |
Response
{
"data": [{
"id": "campaign_123",
"name": "Summer Tournament Promo",
"type": "ONE_TIME",
"status": "SCHEDULED",
"channels": ["EMAIL", "SMS"],
"segmentId": "segment_456",
"scheduledAt": "2025-12-20T09:00:00Z",
"targetCount": 500,
"sentCount": 0,
"openRate": null,
"createdAt": "2025-12-14T10:00:00Z"
}]
}
Get Campaign
GET /campaigns/:id
Create Campaign
POST /campaigns
Request Body
{
"tenantId": "tenant_123",
"clubId": "club_456",
"name": "December Newsletter",
"description": "Monthly member newsletter",
"type": "ONE_TIME",
"segmentId": "segment_123",
"channels": ["EMAIL", "SMS"],
"emailTemplateId": "template_email_123",
"smsTemplateId": "template_sms_456",
"timezone": "Africa/Johannesburg"
}
Update Campaign
PATCH /campaigns/:id
Schedule Campaign
POST /campaigns/:id/schedule
Request Body
{
"scheduledAt": "2025-12-20T09:00:00Z"
}
Response
{
"id": "campaign_123",
"status": "SCHEDULED",
"scheduledAt": "2025-12-20T09:00:00Z",
"targetCount": 500
}
Pause Campaign
POST /campaigns/:id/pause
Resume Campaign
POST /campaigns/:id/resume
Cancel Campaign
POST /campaigns/:id/cancel
Get Campaign Stats
GET /campaigns/:id/stats
Response
{
"campaignId": "campaign_123",
"targetCount": 500,
"sentCount": 485,
"deliveredCount": 478,
"openedCount": 215,
"clickedCount": 89,
"convertedCount": 12,
"unsubscribedCount": 3,
"bouncedCount": 7,
"deliveryRate": 0.9856,
"openRate": 0.4498,
"clickRate": 0.4140,
"conversionRate": 0.1348,
"byChannel": {
"EMAIL": { "sent": 485, "opened": 215, "clicked": 89 },
"SMS": { "sent": 380, "delivered": 375 }
}
}
Journeys
List Journeys
GET /journeys
Query Parameters
| Parameter | Type | Description |
|---|---|---|
tenantId | string | Required |
status | string | DRAFT, ACTIVE, PAUSED, ARCHIVED |
triggerType | string | Filter by trigger event type |
Response
{
"data": [{
"id": "journey_123",
"name": "New Member Onboarding",
"status": "ACTIVE",
"triggerType": "scl.member.created",
"totalEnrolled": 150,
"totalCompleted": 120,
"totalExited": 10,
"stepsCount": 5,
"activatedAt": "2025-11-01T00:00:00Z"
}]
}
Get Journey
GET /journeys/:id
Query Parameters
| Parameter | Type | Description |
|---|---|---|
include | string | steps, enrollments |
Create Journey
POST /journeys
Request Body
{
"tenantId": "tenant_123",
"name": "New Member Onboarding",
"description": "Welcome sequence for new members",
"triggerType": "scl.member.created",
"triggerConfig": {
"conditions": [
{ "field": "membershipTier", "operator": "in", "value": ["Gold", "Platinum"] }
]
},
"allowReentry": false,
"timezone": "Africa/Johannesburg"
}
Get Journey Steps
GET /journeys/:id/steps
Response
{
"data": [
{
"id": "step_1",
"stepNumber": 1,
"type": "SEND",
"name": "Welcome Email",
"config": { "channel": "EMAIL", "templateId": "welcome_email" },
"nextStepId": "step_2"
},
{
"id": "step_2",
"stepNumber": 2,
"type": "WAIT",
"name": "Wait 3 days",
"config": { "duration": "3 days" },
"nextStepId": "step_3"
},
{
"id": "step_3",
"stepNumber": 3,
"type": "CONDITION",
"name": "Check if opened",
"config": {
"rules": [{ "field": "lastEmailOpenAt", "operator": "isNotNull" }]
},
"nextStepYesId": "step_4",
"nextStepNoId": "step_5"
}
]
}
Add Journey Step
POST /journeys/:id/steps
Request Body
{
"type": "SEND",
"name": "Follow-up Email",
"config": {
"channel": "EMAIL",
"templateId": "followup_email"
},
"nextStepId": "step_next"
}
Update Journey Step
PATCH /journeys/:journeyId/steps/:stepId
Delete Journey Step
DELETE /journeys/:journeyId/steps/:stepId
Activate Journey
POST /journeys/:id/activate
Pause Journey
POST /journeys/:id/pause
Resume Journey
POST /journeys/:id/resume
Archive Journey
POST /journeys/:id/archive
Get Journey Enrollments
GET /journeys/:id/enrollments
Query Parameters
| Parameter | Type | Description |
|---|---|---|
status | string | ACTIVE, COMPLETED, EXITED, FAILED |
skip | number | Offset |
take | number | Limit |
Response
{
"data": [{
"id": "enrollment_123",
"customerId": "customer_456",
"customerName": "John Smith",
"status": "ACTIVE",
"currentStepNumber": 3,
"currentStepName": "Wait 3 days",
"waitingUntil": "2025-12-17T09:00:00Z",
"enrolledAt": "2025-12-14T09:00:00Z"
}],
"meta": { "total": 150, "skip": 0, "take": 25 }
}
Manually Enroll Customer
POST /journeys/:id/enrollments
Request Body
{
"customerId": "customer_123"
}
Exit Customer from Journey
DELETE /journeys/:journeyId/enrollments/:customerId
Social Connections
List Connections
GET /social/connections
Query Parameters
| Parameter | Type | Description |
|---|---|---|
tenantId | string | Required |
clubId | string | Filter by club |
platform | string | FACEBOOK, INSTAGRAM, TWITTER, LINKEDIN |
isActive | boolean | Filter active |
Response
{
"data": [{
"id": "connection_123",
"platform": "FACEBOOK",
"accountId": "page_123456",
"accountName": "Randpark Golf Club",
"accountType": "page",
"permissions": ["pages_manage_posts", "pages_read_engagement"],
"isActive": true,
"lastUsedAt": "2025-12-10T14:00:00Z",
"connectedBy": "user_456",
"createdAt": "2025-06-01T00:00:00Z"
}]
}
Initiate OAuth Connection
POST /social/connections/connect
Request Body
{
"tenantId": "tenant_123",
"clubId": "club_456",
"platform": "FACEBOOK",
"redirectUri": "https://app.example.com/social/callback"
}
Response
{
"authUrl": "https://facebook.com/v18.0/dialog/oauth?...",
"state": "encrypted_state_token"
}
Complete OAuth Callback
POST /social/connections/callback
Request Body
{
"platform": "FACEBOOK",
"code": "oauth_authorization_code",
"state": "encrypted_state_token"
}
Response: 201 Created with connection object
Disconnect
DELETE /social/connections/:id
Refresh Token
POST /social/connections/:id/refresh
Social Posts
List Posts
GET /social/posts
Query Parameters
| Parameter | Type | Description |
|---|---|---|
tenantId | string | Required |
connectionId | string | Filter by connection |
campaignId | string | Filter by campaign |
status | string | DRAFT, SCHEDULED, PUBLISHED, FAILED |
Response
{
"data": [{
"id": "post_123",
"connectionId": "connection_456",
"platform": "FACEBOOK",
"accountName": "Randpark Golf Club",
"content": "Join us for the Summer Tournament!",
"mediaUrls": ["https://..."],
"status": "PUBLISHED",
"publishedAt": "2025-12-10T14:00:00Z",
"platformUrl": "https://facebook.com/...",
"likes": 45,
"comments": 12,
"shares": 8,
"reach": 1250
}]
}
Create Post
POST /social/posts
Request Body
{
"tenantId": "tenant_123",
"connectionId": "connection_456",
"campaignId": "campaign_789",
"content": "Join us for the Summer Tournament! Register now.",
"mediaUrls": ["https://cdn.example.com/tournament.jpg"],
"linkUrl": "https://club.example.com/tournaments/summer-2025",
"hashtags": ["golf", "tournament", "summer2025"]
}
Update Post
PATCH /social/posts/:id
Schedule Post
POST /social/posts/:id/schedule
Request Body
{
"scheduledAt": "2025-12-20T09:00:00Z"
}
Publish Post Immediately
POST /social/posts/:id/publish
Cancel Scheduled Post
POST /social/posts/:id/cancel
Sync Engagement Metrics
POST /social/posts/:id/sync-engagement
Fetches latest engagement metrics from the platform.
Event Promotions
List Promotions
GET /promotions
Query Parameters
| Parameter | Type | Description |
|---|---|---|
tenantId | string | Required |
clubId | string | Filter by club |
sourceType | string | COMPETITION, CLUB_EVENT, SPECIAL |
isActive | boolean | Filter active |
Response
{
"data": [{
"id": "promo_123",
"clubId": "club_456",
"sourceType": "COMPETITION",
"sourceId": "competition_789",
"autoPromote": true,
"channels": ["EMAIL", "SMS", "SOCIAL"],
"announceAt": "2025-12-15T09:00:00Z",
"reminderDays": [7, 3, 1],
"segmentId": "segment_123",
"lastPromotedAt": "2025-12-15T09:00:00Z",
"isActive": true
}]
}
Get Promotion
GET /promotions/:id
Create Promotion
POST /promotions
Request Body
{
"tenantId": "tenant_123",
"clubId": "club_456",
"sourceType": "COMPETITION",
"sourceId": "competition_789",
"autoPromote": true,
"channels": ["EMAIL", "SMS", "SOCIAL"],
"announceAt": "2025-12-15T09:00:00Z",
"reminderDays": [7, 3, 1],
"segmentId": "segment_active_golfers",
"emailTemplateId": "tournament_invite",
"smsTemplateId": "tournament_sms",
"socialContent": "Join us for the Championship Tournament!"
}
Update Promotion
PATCH /promotions/:id
Execute Promotion Now
POST /promotions/:id/execute
Manually triggers the promotion campaign.
Delete Promotion
DELETE /promotions/:id
Analytics
Engagement Distribution
GET /analytics/engagement
Churn Risk Overview
GET /analytics/churn-risk
Campaign Analytics
GET /analytics/campaigns
Query Parameters
| Parameter | Type | Description |
|---|---|---|
tenantId | string | Required |
startDate | string | From date |
endDate | string | To date |
Response
{
"summary": {
"totalCampaigns": 24,
"totalSent": 12500,
"avgOpenRate": 0.42,
"avgClickRate": 0.15,
"avgConversionRate": 0.03
},
"byType": {
"ONE_TIME": { "count": 15, "avgOpenRate": 0.45 },
"RECURRING": { "count": 6, "avgOpenRate": 0.38 },
"JOURNEY": { "count": 3, "avgOpenRate": 0.52 }
},
"topPerforming": [
{ "id": "campaign_1", "name": "Summer Sale", "openRate": 0.65 }
]
}
Journey Analytics
GET /analytics/journeys
Response
{
"summary": {
"activeJourneys": 5,
"totalEnrolled": 1250,
"completionRate": 0.78,
"avgTimeToComplete": "5.2 days"
},
"byJourney": [{
"id": "journey_1",
"name": "Onboarding",
"enrolled": 500,
"completed": 420,
"exited": 30,
"active": 50,
"completionRate": 0.84
}]
}
Social Analytics
GET /analytics/social
Response
{
"summary": {
"totalPosts": 45,
"totalReach": 25000,
"totalEngagement": 1500,
"avgEngagementRate": 0.06
},
"byPlatform": {
"FACEBOOK": { "posts": 25, "reach": 15000, "engagement": 900 },
"INSTAGRAM": { "posts": 15, "reach": 8000, "engagement": 500 },
"TWITTER": { "posts": 5, "reach": 2000, "engagement": 100 }
},
"topPosts": [
{ "id": "post_1", "platform": "FACEBOOK", "reach": 2500, "engagement": 150 }
]
}
GraphQL Schema
Queries
type Query {
# Customers
customer(id: ID!): Customer
customers(tenantId: String!, filter: CustomerFilter, pagination: PaginationInput): CustomerConnection!
resolveCustomer(tenantId: String!, email: String, phone: String, authUserId: ID): ResolutionResult
# Timeline & Notes
customerTimeline(customerId: ID!, filters: TimelineFilters): ActivityConnection!
customerNotes(customerId: ID!, type: NoteType): [Note!]!
# Segments
segments(tenantId: String!, type: SegmentType): [Segment!]!
segment(id: ID!): Segment
segmentMembers(segmentId: ID!, pagination: PaginationInput): CustomerConnection!
# Campaigns
campaigns(tenantId: String!, filter: CampaignFilter): CampaignConnection!
campaign(id: ID!): Campaign
campaignStats(id: ID!): CampaignStats!
# Journeys
journeys(tenantId: String!, status: JourneyStatus): [Journey!]!
journey(id: ID!): Journey
journeySteps(journeyId: ID!): [JourneyStep!]!
journeyEnrollments(journeyId: ID!, status: EnrollmentStatus): JourneyEnrollmentConnection!
# Social
socialConnections(tenantId: String!, platform: SocialPlatform): [SocialConnection!]!
socialPosts(tenantId: String!, filter: SocialPostFilter): SocialPostConnection!
# Promotions
eventPromotions(tenantId: String!, clubId: String): [EventPromotion!]!
# Analytics
engagementAnalytics(tenantId: String!): EngagementAnalytics!
campaignAnalytics(tenantId: String!, dateRange: DateRangeInput): CampaignAnalytics!
journeyAnalytics(tenantId: String!): JourneyAnalytics!
socialAnalytics(tenantId: String!, dateRange: DateRangeInput): SocialAnalytics!
}
Mutations
type Mutation {
# Customers
createCustomer(input: CreateCustomerInput!): Customer!
updateCustomer(id: ID!, input: UpdateCustomerInput!): Customer!
mergeCustomers(winnerId: ID!, loserId: ID!): MergeResult!
# Notes
addNote(customerId: ID!, input: CreateNoteInput!): Note!
updateNote(id: ID!, input: UpdateNoteInput!): Note!
deleteNote(id: ID!): Boolean!
# Segments
createSegment(input: CreateSegmentInput!): Segment!
updateSegment(id: ID!, input: UpdateSegmentInput!): Segment!
refreshSegment(id: ID!): RefreshResult!
syncSegmentToMessaging(id: ID!): SyncResult!
# Campaigns
createCampaign(input: CreateCampaignInput!): Campaign!
updateCampaign(id: ID!, input: UpdateCampaignInput!): Campaign!
scheduleCampaign(id: ID!, scheduledAt: DateTime!): Campaign!
pauseCampaign(id: ID!): Campaign!
resumeCampaign(id: ID!): Campaign!
cancelCampaign(id: ID!): Campaign!
# Journeys
createJourney(input: CreateJourneyInput!): Journey!
updateJourney(id: ID!, input: UpdateJourneyInput!): Journey!
addJourneyStep(journeyId: ID!, input: CreateJourneyStepInput!): JourneyStep!
updateJourneyStep(journeyId: ID!, stepId: ID!, input: UpdateJourneyStepInput!): JourneyStep!
deleteJourneyStep(journeyId: ID!, stepId: ID!): Boolean!
activateJourney(id: ID!): Journey!
pauseJourney(id: ID!): Journey!
archiveJourney(id: ID!): Journey!
enrollCustomerInJourney(journeyId: ID!, customerId: ID!): JourneyEnrollment!
exitCustomerFromJourney(journeyId: ID!, customerId: ID!): Boolean!
# Social
initiateSocialConnection(input: InitiateSocialConnectionInput!): OAuthResult!
completeSocialConnection(input: CompleteSocialConnectionInput!): SocialConnection!
disconnectSocial(id: ID!): Boolean!
createSocialPost(input: CreateSocialPostInput!): SocialPost!
updateSocialPost(id: ID!, input: UpdateSocialPostInput!): SocialPost!
scheduleSocialPost(id: ID!, scheduledAt: DateTime!): SocialPost!
publishSocialPost(id: ID!): SocialPost!
# Promotions
createEventPromotion(input: CreateEventPromotionInput!): EventPromotion!
updateEventPromotion(id: ID!, input: UpdateEventPromotionInput!): EventPromotion!
executeEventPromotion(id: ID!): ExecutePromotionResult!
}
Key Types
type Campaign {
id: ID!
name: String!
type: CampaignType!
status: CampaignStatus!
channels: [Channel!]!
segment: Segment
scheduledAt: DateTime
targetCount: Int!
sentCount: Int!
openRate: Float
clickRate: Float
createdAt: DateTime!
}
type Journey {
id: ID!
name: String!
status: JourneyStatus!
triggerType: String!
allowReentry: Boolean!
totalEnrolled: Int!
totalCompleted: Int!
steps: [JourneyStep!]!
activatedAt: DateTime
}
type JourneyStep {
id: ID!
stepNumber: Int!
type: JourneyStepType!
name: String
config: JSON!
nextStepId: ID
nextStepYesId: ID
nextStepNoId: ID
}
type SocialConnection {
id: ID!
platform: SocialPlatform!
accountId: String!
accountName: String!
isActive: Boolean!
lastUsedAt: DateTime
}
type SocialPost {
id: ID!
connection: SocialConnection!
content: String!
status: SocialPostStatus!
scheduledAt: DateTime
publishedAt: DateTime
likes: Int!
comments: Int!
shares: Int!
reach: Int!
}
# Enums
enum CampaignType { ONE_TIME, RECURRING, TRIGGERED, JOURNEY }
enum CampaignStatus { DRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, CANCELLED }
enum JourneyStatus { DRAFT, ACTIVE, PAUSED, ARCHIVED }
enum JourneyStepType { TRIGGER, SEND, WAIT, CONDITION, SPLIT, UPDATE, END }
enum SocialPlatform { FACEBOOK, INSTAGRAM, TWITTER, LINKEDIN, TIKTOK }
enum SocialPostStatus { DRAFT, SCHEDULED, PUBLISHING, PUBLISHED, FAILED }
enum Channel { EMAIL, SMS, PUSH, WHATSAPP, IN_APP, SOCIAL }
Error Responses
{
"error": {
"code": "CAMPAIGN_NOT_FOUND",
"message": "Campaign not found",
"requestId": "req_123"
}
}
Error Codes
| Code | HTTP | Description |
|---|---|---|
CUSTOMER_NOT_FOUND | 404 | Customer not found |
SEGMENT_NOT_FOUND | 404 | Segment not found |
CAMPAIGN_NOT_FOUND | 404 | Campaign not found |
JOURNEY_NOT_FOUND | 404 | Journey not found |
CONNECTION_NOT_FOUND | 404 | Social connection not found |
INVALID_CAMPAIGN_STATUS | 400 | Invalid status transition |
INVALID_JOURNEY_STATUS | 400 | Invalid status transition |
JOURNEY_NO_STEPS | 400 | Journey has no steps |
OAUTH_FAILED | 400 | OAuth flow failed |
SOCIAL_PUBLISH_FAILED | 500 | Failed to publish to platform |
UNAUTHORIZED | 401 | Invalid token |
FORBIDDEN | 403 | Insufficient permissions |
RATE_LIMITED | 429 | Too many requests |
Rate Limits
| Endpoint | Limit |
|---|---|
| Read operations | 1000/minute |
| Write operations | 100/minute |
| Campaign execution | 10/minute |
| Social publishing | 30/minute |
| Bulk operations | 10/minute |