Skip to main content

Overview

The SalesforceClient actor provides a Swift interface to Salesforce’s APIs. It handles authentication and provides type-safe access to various API endpoints. This is a lower-level client that requires manual authentication management. For a higher-level interface, use CongregationKit.

Initialization

init(httpClient:)

Creates a new Salesforce API client.
httpClient
HTTPClient
required
The AsyncHTTPClient instance to use for making requests
import AsyncHTTPClient
import SalesforceClient

let httpClient = HTTPClient(eventLoopGroupProvider: .shared)
let salesforce = SalesforceClient(httpClient: httpClient)

Properties

auth

Routes for Salesforce authentication.
auth
SalesforceAuthRoutes
Authentication routes for OAuth 2.0 password flow
let credentials = SalesforceCredentials(
    clientId: "your_client_id",
    clientSecret: "your_client_secret",
    username: "your_username",
    password: "your_password"
)

let authResponse = try await salesforce.auth.authenticate(credentials: credentials)
print("Access token: \(authResponse.accessToken)")
print("Instance URL: \(authResponse.instanceUrl)")

members

Routes for Salesforce member management.
members
SalesforceMemberRoutes
Member routes for fetching and managing member data
let authResponse = try await salesforce.auth.authenticate(credentials: credentials)

let response = try await salesforce.members.fetchAll(
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl,
    pageNumber: 1,
    pageSize: 50
)

for member in response.members {
    print(member.memberName ?? "Unknown")
}

seekers

Routes for Salesforce seeker management.
seekers
SalesforceSeekerRoutes
Seeker routes for fetching and managing seeker data
let seekerResponse = try await salesforce.seekers.fetchAll(
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl,
    pageNumber: 1,
    pageSize: 50,
    seekerId: nil,
    name: nil,
    campus: .eastCampus,
    leadStatus: .followUp,
    email: nil,
    leadId: nil,
    contactNumber: nil
)

files

Routes for Salesforce file downloads.
files
SalesforceFilesRoutes
File routes for downloading files from Salesforce
let fileResponse = try await salesforce.files.download(
    recordId: "a0x2w000002jxqn",
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl
)

Authentication Methods

authenticate(credentials:)

Authenticates with Salesforce using OAuth 2.0 password flow.
credentials
SalesforceCredentials
required
The Salesforce credentials
returns
SalesforceAuthResponse
Authentication response containing access token and instance URL
throws
SalesforceAuthError
Throws if authentication fails
let credentials = SalesforceCredentials(
    clientId: "your_client_id",
    clientSecret: "your_client_secret",
    username: "your_username",
    password: "your_password"
)

let authResponse = try await salesforce.auth.authenticate(credentials: credentials)

Member Methods

fetchAll(accessToken:instanceUrl:pageNumber:pageSize:)

Fetches all members from Salesforce with pagination support.
accessToken
String
required
The OAuth access token
instanceUrl
String
required
The Salesforce instance URL
pageNumber
Int?
The page number to fetch (optional, default 1)
pageSize
Int?
The page size to fetch (optional, default 50)
returns
MemberResponse
MemberResponse containing members and pagination info
throws
MemberError
Throws if fetch fails
let response = try await salesforce.members.fetchAll(
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl,
    pageNumber: 1,
    pageSize: 100
)

print("Total members: \(response.metadata?.total ?? 0)")
for member in response.members {
    print(member.memberName ?? "Unknown")
}

fetchAll(accessToken:instanceUrl:pageNumber:pageSize:nextPageToken:)

Fetches all members with cursor-based pagination support.
accessToken
String
required
The OAuth access token
instanceUrl
String
required
The Salesforce instance URL
pageNumber
Int?
The page number to fetch (optional, default 1)
pageSize
Int?
The page size to fetch (optional, default 50)
nextPageToken
String?
The next page token for cursor-based pagination (optional)
returns
MemberResponse
MemberResponse containing members and pagination info
// First page
var response = try await salesforce.members.fetchAll(
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl,
    pageNumber: 1,
    pageSize: 50,
    nextPageToken: nil
)

// Next page using token
if let nextToken = response.metadata?.nextPageToken {
    response = try await salesforce.members.fetchAll(
        accessToken: authResponse.accessToken,
        instanceUrl: authResponse.instanceUrl,
        pageNumber: nil,
        pageSize: 50,
        nextPageToken: nextToken
    )
}

fetch(memberId:accessToken:instanceUrl:)

Fetches a specific member by ID.
memberId
MemberID
required
The member ID to fetch (must start with TKT)
accessToken
String
required
The OAuth access token
instanceUrl
String
required
The Salesforce instance URL
returns
Member
The member if found
throws
MemberError
Throws if member not found or fetch fails
let memberId = try MemberID(validating: "TKT123456")
let member = try await salesforce.members.fetch(
    memberId: memberId,
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl
)

print("Member: \(member.memberName ?? "Unknown")")

fetchAll(accessToken:instanceUrl:pageNumber:pageSize:expanded:)

Fetches all members with optional expanded fields.
accessToken
String
required
The OAuth access token
instanceUrl
String
required
The Salesforce instance URL
pageNumber
Int?
The page number to fetch (optional, default 1)
pageSize
Int?
The page size to fetch (optional, default 50)
expanded
[MemberExpand]
required
Array specifying which related information to expand
returns
MemberResponse
MemberResponse containing members with expanded fields
let response = try await salesforce.members.fetchAll(
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl,
    pageNumber: 1,
    pageSize: 50,
    expanded: [.contactInformation, .employmentInformation]
)

fetch(memberId:accessToken:instanceUrl:expanded:)

Fetches a specific member by ID with optional expanded information.
memberId
MemberID
required
The member ID to fetch
accessToken
String
required
The OAuth access token
instanceUrl
String
required
The Salesforce instance URL
expanded
[MemberExpand]
required
Array specifying which related information to expand
returns
Member
The member if found with expanded fields
let memberId = try MemberID(validating: "TKT123456")
let member = try await salesforce.members.fetch(
    memberId: memberId,
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl,
    expanded: [.contactInformation, .discipleshipInformation]
)

Seeker Methods

fetchAll(accessToken:instanceUrl:pageNumber:pageSize:seekerId:name:campus:leadStatus:email:leadId:contactNumber:)

Fetches a paginated list of seekers from Salesforce.
accessToken
String
required
The OAuth access token
instanceUrl
String
required
The Salesforce instance URL
pageNumber
Int?
The page number to fetch (optional, default 1)
pageSize
Int?
The page size to fetch (optional, default 50)
seekerId
String?
Filter by seeker ID (optional)
name
String?
Filter by name (optional)
campus
Campus?
Filter by campus (optional)
leadStatus
LeadStatus?
Filter by lead status (optional)
email
String?
Filter by email (optional)
leadId
String?
Filter by lead ID (optional)
contactNumber
String?
Filter by contact number (optional)
returns
SeekerResponse
SeekerResponse containing seekers and pagination info
throws
SeekerError
Throws if fetch fails
let response = try await salesforce.seekers.fetchAll(
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl,
    pageNumber: 1,
    pageSize: 50,
    seekerId: nil,
    name: "John",
    campus: .eastCampus,
    leadStatus: .followUp,
    email: nil,
    leadId: nil,
    contactNumber: nil
)

fetch(identifier:accessToken:instanceUrl:)

Fetches a specific seeker by identifier (leadId or phone).
identifier
String
required
The identifier to fetch (leadId or phone, case-insensitive)
accessToken
String
required
The OAuth access token
instanceUrl
String
required
The Salesforce instance URL
returns
Seeker
The seeker if found
throws
SeekerError
Throws if seeker not found or fetch fails
let seeker = try await salesforce.seekers.fetch(
    identifier: "LEAD001",
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl
)

fetchAll(accessToken:instanceUrl:)

Fetches all seekers from Salesforce (non-paginated).
accessToken
String
required
The OAuth access token
instanceUrl
String
required
The Salesforce instance URL
returns
[Seeker]
Array of seekers
throws
SeekerError
Throws if fetch fails
let seekers = try await salesforce.seekers.fetchAll(
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl
)

File Methods

download(recordId:accessToken:instanceUrl:)

Downloads a file from Salesforce using a record ID.
recordId
String
required
The record ID to download the file from (15-18 character alphanumeric Salesforce ID)
accessToken
String
required
The OAuth access token
instanceUrl
String
required
The Salesforce instance URL
returns
FileDownloadResponse
File download response containing file data and metadata
throws
FileDownloadError
Throws if download fails
let fileResponse = try await salesforce.files.download(
    recordId: "a0x2w000002jxqn",
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl
)

print("Downloaded: \(fileResponse.fullFilename)")
print("Size: \(fileResponse.fileSize) bytes")

// Save to disk
try fileResponse.data.write(to: URL(fileURLWithPath: "/tmp/\(fileResponse.fullFilename)"))

Convenience Methods

authenticateAndFetchMembers(credentials:)

Authenticates with Salesforce and fetches all members in one operation.
credentials
SalesforceCredentials
required
The Salesforce credentials
returns
[Member]
Array of church members
throws
SalesforceAuthError | MemberError
Throws if operation fails
let members = try await salesforce.authenticateAndFetchMembers(
    credentials: credentials
)

for member in members {
    print(member.memberName ?? "Unknown")
}

authenticateAndFetchMember(credentials:memberId:)

Authenticates with Salesforce and fetches a specific member.
credentials
SalesforceCredentials
required
The Salesforce credentials
memberId
String
required
The member ID to fetch
returns
Member
The member if found
throws
SalesforceAuthError | MemberError
Throws if operation fails
let member = try await salesforce.authenticateAndFetchMember(
    credentials: credentials,
    memberId: "TKT123456"
)

authenticateAndDownloadFile(credentials:recordId:)

Authenticates with Salesforce and downloads a file.
credentials
SalesforceCredentials
required
The Salesforce credentials
recordId
String
required
The record ID to download the file from
returns
FileDownloadResponse
File download response containing file data and metadata
throws
SalesforceAuthError | FileDownloadError
Throws if operation fails
let fileResponse = try await salesforce.authenticateAndDownloadFile(
    credentials: credentials,
    recordId: "a0x2w000002jxqn"
)

Complete Example

import AsyncHTTPClient
import SalesforceClient

func main() async throws {
    let httpClient = HTTPClient(eventLoopGroupProvider: .shared)
    defer { try? httpClient.syncShutdown() }
    
    let salesforce = SalesforceClient(httpClient: httpClient)
    
    // Authenticate
    let credentials = SalesforceCredentials(
        clientId: ProcessInfo.processInfo.environment["SALESFORCE_CLIENT_ID"]!,
        clientSecret: ProcessInfo.processInfo.environment["SALESFORCE_CLIENT_SECRET"]!,
        username: ProcessInfo.processInfo.environment["SALESFORCE_USERNAME"]!,
        password: ProcessInfo.processInfo.environment["SALESFORCE_PASSWORD"]!
    )
    
    let authResponse = try await salesforce.auth.authenticate(credentials: credentials)
    print("Authenticated successfully")
    
    // Fetch members
    let memberResponse = try await salesforce.members.fetchAll(
        accessToken: authResponse.accessToken,
        instanceUrl: authResponse.instanceUrl,
        pageNumber: 1,
        pageSize: 50
    )
    
    print("Total members: \(memberResponse.metadata?.total ?? 0)")
    
    for member in memberResponse.members {
        print("\(member.memberName ?? "Unknown")")
    }
    
    // Fetch a specific member
    let memberId = try MemberID(validating: "TKT123456")
    let member = try await salesforce.members.fetch(
        memberId: memberId,
        accessToken: authResponse.accessToken,
        instanceUrl: authResponse.instanceUrl
    )
    
    print("Found member: \(member.memberName ?? "Unknown")")
    
    // Fetch seekers with filters
    let seekerResponse = try await salesforce.seekers.fetchAll(
        accessToken: authResponse.accessToken,
        instanceUrl: authResponse.instanceUrl,
        pageNumber: 1,
        pageSize: 50,
        seekerId: nil,
        name: nil,
        campus: .eastCampus,
        leadStatus: .followUp,
        email: nil,
        leadId: nil,
        contactNumber: nil
    )
    
    for seeker in seekerResponse.seekers {
        print("Seeker: \(seeker.fullName ?? "Unknown")")
    }
    
    // Download a file
    let file = try await salesforce.files.download(
        recordId: "a0x2w000002jxqn",
        accessToken: authResponse.accessToken,
        instanceUrl: authResponse.instanceUrl
    )
    
    try file.data.write(to: URL(fileURLWithPath: "/tmp/\(file.fullFilename)"))
    print("Downloaded \(file.fullFilename)")
}

try await main()

Error Handling

do {
    let salesforce = SalesforceClient(httpClient: httpClient)
    
    let authResponse = try await salesforce.auth.authenticate(credentials: credentials)
    
    let member = try await salesforce.members.fetch(
        memberId: try MemberID(validating: "TKT123456"),
        accessToken: authResponse.accessToken,
        instanceUrl: authResponse.instanceUrl
    )
    
} catch let error as SalesforceAuthError {
    switch error {
    case .invalidCredentials:
        print("Invalid credentials")
    case .networkError(let underlying):
        print("Network error: \(underlying)")
    case .invalidResponse:
        print("Invalid response from Salesforce")
    case .serverError(let message):
        print("Server error: \(message)")
    case .rateLimitExceeded:
        print("Rate limit exceeded")
    }
} catch let error as MemberError {
    switch error {
    case .memberNotFound:
        print("Member not found")
    case .invalidMemberID:
        print("Invalid member ID format")
    case .fetchFailed(let underlying):
        print("Fetch failed: \(underlying)")
    case .invalidMemberData:
        print("Invalid member data")
    }
} catch let error as FileDownloadError {
    switch error {
    case .fileNotFound:
        print("File not found")
    case .invalidRecordId:
        print("Invalid record ID")
    case .downloadFailed(let underlying):
        print("Download failed: \(underlying)")
    default:
        print("File error: \(error.localizedDescription)")
    }
} catch {
    print("Unexpected error: \(error)")
}

Comparison with CongregationKit

SalesforceClient is a lower-level client that requires manual authentication management. Use it when you need:
  • Fine-grained control over authentication
  • Custom authentication flows
  • Direct access to Salesforce APIs
For most use cases, CongregationKit provides a simpler, higher-level interface:
// SalesforceClient (manual auth)
let salesforce = SalesforceClient(httpClient: httpClient)
let authResponse = try await salesforce.auth.authenticate(credentials: credentials)
let members = try await salesforce.members.fetchAll(
    accessToken: authResponse.accessToken,
    instanceUrl: authResponse.instanceUrl,
    pageNumber: 1,
    pageSize: 50
)

// CongregationKit (automatic auth)
let congregation = try await CongregationKit(
    httpClient: httpClient,
    credentials: credentials
)
let members = try await congregation.members.fetchAll(
    pageNumber: 1,
    pageSize: 50
)

Build docs developers (and LLMs) love