Skip to main content

CRM & Marketing Platform API Reference

Version: 2.0 Base URL: /api/v1 Authentication: Bearer JWT Token


Table of Contents

  1. Authentication
  2. Customer Profiles
  3. Identity Resolution
  4. Activity Timeline
  5. Customer Notes
  6. Segments
  7. Campaigns
  8. Journeys
  9. Social Connections
  10. Social Posts
  11. Event Promotions
  12. Analytics
  13. GraphQL Schema
  14. Error Responses

Authentication

All endpoints require authentication via Bearer token.

Authorization: Bearer <jwt_token>

Required Scopes

ScopeDescription
crm:readRead profiles, timeline, notes
crm:writeCreate/update profiles, add notes
crm:adminMerge profiles, manage segments
crm:campaignsManage campaigns and journeys
crm:socialManage social connections and posts

Customer Profiles

List Customers

GET /customers

Query Parameters

ParameterTypeDescription
tenantIdstringTenant ID (required)
statusstringFilter: ACTIVE, INACTIVE, MERGED, DELETED
lifecycleStagestringFilter: SUBSCRIBER, LEAD, CUSTOMER, MEMBER, ADVOCATE, CHURNED
searchstringSearch by name, email, phone
segmentIdstringFilter by segment membership
engagementMinnumberMin engagement score (0-100)
churnRiskMinnumberMin churn risk (0-100)
emailOptInbooleanFilter by opt-in
skipnumberOffset (default: 0)
takenumberLimit (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

ParameterTypeDescription
tenantIdstringRequired
authUserIdstringIDP user ID
emailstringEmail
phonestringPhone (E.164)

Response

{
"profile": { /* customer */ },
"resolution": {
"isNew": false,
"matchedBy": ["email"],
"confidence": 95
}
}

Activity Timeline

Get Timeline

GET /customers/:id/activities

Query Parameters

ParameterTypeDescription
categoriesstring[]MEMBERSHIP, BOOKING, COMMUNICATION, ENGAGEMENT, FINANCIAL, GOLF, MARKETING, SOCIAL, SYSTEM
startDatestringISO 8601 date
endDatestringISO 8601 date
skipnumberOffset
takenumberLimit

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

ParameterTypeDescription
tenantIdstringRequired
typestringSTATIC, DYNAMIC, SMART
isActivebooleanFilter 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

ParameterTypeDescription
tenantIdstringRequired
clubIdstringFilter by club
statusstringDRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, CANCELLED
typestringONE_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

ParameterTypeDescription
tenantIdstringRequired
statusstringDRAFT, ACTIVE, PAUSED, ARCHIVED
triggerTypestringFilter 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

ParameterTypeDescription
includestringsteps, 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

ParameterTypeDescription
statusstringACTIVE, COMPLETED, EXITED, FAILED
skipnumberOffset
takenumberLimit

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

ParameterTypeDescription
tenantIdstringRequired
clubIdstringFilter by club
platformstringFACEBOOK, INSTAGRAM, TWITTER, LINKEDIN
isActivebooleanFilter 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

ParameterTypeDescription
tenantIdstringRequired
connectionIdstringFilter by connection
campaignIdstringFilter by campaign
statusstringDRAFT, 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

ParameterTypeDescription
tenantIdstringRequired
clubIdstringFilter by club
sourceTypestringCOMPETITION, CLUB_EVENT, SPECIAL
isActivebooleanFilter 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

ParameterTypeDescription
tenantIdstringRequired
startDatestringFrom date
endDatestringTo 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

CodeHTTPDescription
CUSTOMER_NOT_FOUND404Customer not found
SEGMENT_NOT_FOUND404Segment not found
CAMPAIGN_NOT_FOUND404Campaign not found
JOURNEY_NOT_FOUND404Journey not found
CONNECTION_NOT_FOUND404Social connection not found
INVALID_CAMPAIGN_STATUS400Invalid status transition
INVALID_JOURNEY_STATUS400Invalid status transition
JOURNEY_NO_STEPS400Journey has no steps
OAUTH_FAILED400OAuth flow failed
SOCIAL_PUBLISH_FAILED500Failed to publish to platform
UNAUTHORIZED401Invalid token
FORBIDDEN403Insufficient permissions
RATE_LIMITED429Too many requests

Rate Limits

EndpointLimit
Read operations1000/minute
Write operations100/minute
Campaign execution10/minute
Social publishing30/minute
Bulk operations10/minute