API reference
The complete public surface of the Cluestick Swift SDK at 0.5.0. Anything not listed here is internal and may change without a version bump.
Entry points
Cluestickis an enum used as a namespace — every member is static, and there is nothing to instantiate.
configure
public static func configure(
apiKey: String,
baseURL: URL = URL(string: "https://api.cluestick.io")!,
identityTokenProvider: (@Sendable () async throws -> String)? = nil
)Call once at launch. baseURL only needs overriding if you are pointed at a non-production environment. See Identity for the token provider.
identify
public static func identify() async throwsExchanges a token from your provider for an identified session, merging any anonymous history. Throws CluestickError.notConfigured if no provider was supplied.
setProfile
public static func setProfile(
serviceIds: [String: String]? = nil,
traits: [String: CluestickTraitValue]? = nil
)reset
public static func reset()Clears the stored session and starts a fresh anonymous one. Call at sign-out.
sdkVersion
public static let sdkVersion = "0.5.0"Messaging
public static func send(
_ message: String, attachments: [Attachment] = []
) async throws
public static func fetchMessages(
after cursor: String? = nil
) async throws -> CluestickMessagePage
public static func fetchAllMessages(
after cursor: String? = nil
) async throws -> CluestickMessagePage
public static var onNewMessages: (@MainActor @Sendable () -> Void)?fetchMessages returns the current conversation; fetchAllMessages spans every conversation this user has had. Assign onNewMessagesto refresh your own UI — a badge, an unread dot — when a reply arrives. It is called on the main actor.
Message types
public struct CluestickMessage: Identifiable, Equatable, Sendable {
public let id: String
public let conversationId: String
public let direction: CluestickMessageDirection
public let author: String?
public let body: String
public let createdAt: Date
public let attachments: [CluestickAttachment]
}
public enum CluestickMessageDirection: Sendable, Equatable {
case inbound // written on this device, by your user
case outbound // a reply from your team
}
public struct CluestickMessagePage: Equatable, Sendable {
public let messages: [CluestickMessage]
public let cursor: String?
public let hasMore: Bool
}Attachments
public struct Attachment: Sendable {
public let filename: String
public let contentType: String
public init(fileURL: URL, filename: String, contentType: String)
public init(data: Data, filename: String, contentType: String)
public init?(fileURL: URL) // infers filename and type from the extension
}Send them with send(_:attachments:). Limits are 25 MB per file and 10 files per message. Recognised extensions include png, jpg, jpeg, gif, webp, heic, heif, mp4, mov, webm, pdf, txt, csv, json, zip, docx, xlsx and pptx.
Attachments you receive arrive on a message as:
public struct CluestickAttachment: Identifiable, Equatable, Sendable {
public let id: String
public let filename: String
public let contentType: String
public let byteSize: Int
public let status: Status // .pending or .ready
public let url: URL?
public var isImage: Bool
public var isVideo: Bool
}Feature board
public static func fetchFeatureRequests() async throws -> [CluestickFeatureRequest]
public static func vote(forFeatureRequest id: String) async throws
public static func removeVote(forFeatureRequest id: String) async throws
public struct CluestickFeatureRequest: Identifiable, Equatable, Sendable {
public enum Status: String, Decodable, Equatable, Sendable {
case underReview = "under_review"
case planned
case shipped
}
public let id: String
public let title: String
public let description: [CluestickBlock]
public let status: Status
public let target: String?
public let voteCount: Int
public let votedByMe: Bool
public let createdAt: Date
}Voting on a closed request throws CluestickError.votingClosed.
Help centre
public static func fetchHelpCenter() async throws -> [CluestickHelpCollection]
public static func fetchArticle(id: String) async throws -> CluestickArticle
public static func reportArticleViewed(id: String)
public struct CluestickHelpCollection: Identifiable, Equatable, Sendable {
public let id: String
public let name: String
public let articles: [CluestickArticleSummary]
}
public struct CluestickArticleSummary: Identifiable, Hashable, Sendable {
public let id: String
public let title: String
public let updatedAt: Date
public let readMinutes: Int
}
public struct CluestickArticle: Identifiable, Equatable, Sendable {
public let id: String
public let title: String
public let updatedAt: Date
public let readMinutes: Int
public let blocks: [CluestickBlock]
}reportArticleViewed is fire-and-forget: it neither suspends nor throws, so it is safe to call from onAppear. CluestickArticleSummary is Hashable so it works directly as a SwiftUI navigation value.
Content blocks
public enum CluestickBlock: Equatable, Sendable {
case paragraph(String)
case heading(String)
case list([String])
case media(url: URL?, caption: String?)
}Push notifications
public static func setPushToken(_ deviceToken: Data)
public static func isCluestickNotification(_ userInfo: [AnyHashable: Any]) -> Bool
public static func conversationId(fromNotification userInfo: [AnyHashable: Any]) -> String?Hand setPushToken the token from didRegisterForRemoteNotificationsWithDeviceToken. Use isCluestickNotification to decide whether an incoming payload is yours before acting on it.
Trait values
public enum CluestickTraitValue: Encodable, Equatable, Sendable {
case string(String)
case int(Int)
case double(Double)
case bool(Bool)
}It conforms to the string, integer, float and boolean literal protocols, which is why ["plan": "annual", "seats": 3] compiles without wrapping each value.
Errors
public enum CluestickError: Error, Equatable, Sendable {
case notConfigured
case unauthorized
case invalidPayload(message: String)
case notFound
case votingClosed
case server(statusCode: Int)
}CluestickUI
Every symbol below requires iOS 16 or macOS 13. Each view supplies its own NavigationStack and Done button, so it is complete sheet content on its own.
Views
public struct CluestickChatView: View { public init() }
public struct CluestickFeatureBoardView: View { public init() }
public struct CluestickHelpCenterView: View { public init() }
public struct CluestickSupportView: View { public init() }CluestickSupportView is the hub: search, a chat card showing the last reply and an unread dot, with articles and the board as pushed destinations. Use it when you want one entry point rather than three.
Sheet modifiers
public func cluestickChatSheet(isPresented: Binding<Bool>) -> some View
public func cluestickFeatureBoardSheet(isPresented: Binding<Bool>) -> some View
public func cluestickHelpCenterSheet(isPresented: Binding<Bool>) -> some View
public func cluestickSupportSheet(isPresented: Binding<Bool>) -> some ViewTheming
public func cluestickTheme(_ theme: CluestickTheme) -> some View
public struct CluestickTheme: Sendable {
public var accent: Color?
public var fieldBackground: Color?
public var cornerRadius: CGFloat
public var headerTitle: String?
public var headerSubtitle: String?
public init(
accent: Color? = nil,
fieldBackground: Color? = nil,
cornerRadius: CGFloat = 12,
headerTitle: String? = nil,
headerSubtitle: String? = nil
)
}The theme travels through the environment, so it applies to all four Cluestick surfaces and reaches views presented as sheets. Set it once, high up.
- Leave
accentandfieldBackgroundnilto inherit your app’s own tint and system materials. headerTitleandheaderSubtitlename the team the user thinks they are writing to — usually your product, not “Cluestick”.