A Swift Package for Server-Side and Command-Line Access to CloudKit Web Services
MistKit provides a modern Swift interface to CloudKit Web Services REST API, enabling cross-platform CloudKit access for server-side Swift applications, command-line tools, and platforms where the CloudKit framework isn't available.
Built with Swift concurrency (async/await) and designed for modern Swift applications, MistKit supports all three CloudKit authentication methods and provides type-safe access to CloudKit operations.
- 🌍 Cross-Platform Support: Works on macOS, iOS, tvOS, watchOS, visionOS, and Linux
- ⚡ Modern Swift: Built with Swift 6 concurrency features and structured error handling
- 🔐 Multiple Authentication Methods: API token, web authentication, and server-to-server authentication
- 🛡️ Type-Safe: Comprehensive type safety with Swift's type system
- 📋 OpenAPI-Based: Generated from CloudKit Web Services OpenAPI specification using swift-openapi-generator
- 🔒 Secure: Built-in security best practices and credential management
Add MistKit to your Package.swift:
dependencies: [
.package(url: "https://github.com/brightdigit/MistKit.git", from: "1.0.0-beta.1")
]Or add it through Xcode:
- File → Add Package Dependencies
- Enter:
https://github.com/brightdigit/MistKit.git - Select version and add to your target
- Swift 6.1+
- Xcode 16.0+ (for iOS/macOS development)
- Linux: Ubuntu 18.04+ with Swift 6.1+
| Platform | Minimum Version | Server-to-Server Auth |
|---|---|---|
| macOS | 10.15+ | 11.0+ |
| iOS | 13.0+ | 14.0+ |
| tvOS | 13.0+ | 14.0+ |
| watchOS | 6.0+ | 7.0+ |
| visionOS | 1.0+ | 1.0+ |
| Linux | Ubuntu 18.04+ | ✅ |
| Windows | 10+ | ✅ |
MistKit supports three credential types via the Credentials value. The service
does not carry a database — each operation picks its database (and signing
method, for the public database) at the call site.
import MistKit
let credentials = try Credentials(
apiAuth: APICredentials(
apiToken: ProcessInfo.processInfo.environment["CLOUDKIT_API_TOKEN"]!
)
)
let service = CloudKitService(
containerIdentifier: "iCloud.com.example.MyApp",
credentials: credentials
)let credentials = try Credentials(
apiAuth: APICredentials(
apiToken: ProcessInfo.processInfo.environment["CLOUDKIT_API_TOKEN"]!,
webAuthToken: userWebAuthToken
)
)
let service = CloudKitService(
containerIdentifier: "iCloud.com.example.MyApp",
credentials: credentials
)let credentials = try Credentials(
serverToServer: ServerToServerCredentials(
keyID: ProcessInfo.processInfo.environment["CLOUDKIT_KEY_ID"]!,
privateKey: .file(path: "private_key.pem")
)
)
let service = CloudKitService(
containerIdentifier: "iCloud.com.example.MyApp",
credentials: credentials,
environment: .production
)Provide both apiAuth and serverToServer to a single Credentials when one
service must hit public-database routes via S2S signing and user-context
routes via web-auth — MistKit picks the appropriate token manager per call.
let records = try await service.queryRecords(
recordType: "Post",
database: .public(.prefers(.serverToServer))
)Database.public carries a PublicAuthPreference:
.prefers(.serverToServer) / .prefers(.webAuth) (fall back if not configured)
or .requires(.serverToServer) / .requires(.webAuth) (throw if not configured).
Private/shared always use web-auth.
-
Get API Token:
- Log into Apple Developer Console
- Navigate to CloudKit Database
- Generate an API Token
-
Set Environment Variable:
export CLOUDKIT_API_TOKEN="your_api_token_here"
-
Use in Code:
let credentials = try Credentials( apiAuth: APICredentials( apiToken: ProcessInfo.processInfo.environment["CLOUDKIT_API_TOKEN"]! ) ) let service = CloudKitService( containerIdentifier: "iCloud.com.example.MyApp", credentials: credentials )
Web authentication enables user-specific operations and requires both an API token and a web authentication token. The token can be obtained either through CloudKit JS authentication (browser flow) or from an iOS/macOS app via CKFetchWebAuthTokenOperation, which exchanges the user's existing iCloud session for a token your backend can use.
let credentials = try Credentials(
apiAuth: APICredentials(apiToken: apiToken, webAuthToken: webAuthToken)
)
let service = CloudKitService(
containerIdentifier: "iCloud.com.example.MyApp",
credentials: credentials
)Server-to-server authentication provides enterprise-level access using ECDSA P-256 key signing. Note that this method only supports the public database.
-
Generate Key Pair:
# Generate private key openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem # Extract public key openssl ec -in private_key.pem -pubout -out public_key.pem
-
Upload Public Key: Upload the public key to Apple Developer Console
-
Use in Code (the simplest path —
Credentialsresolves the PEM at first use):let credentials = try Credentials( serverToServer: ServerToServerCredentials( keyID: "your_key_id", privateKey: .file(path: "private_key.pem") ) ) let service = CloudKitService( containerIdentifier: "iCloud.com.example.MyApp", credentials: credentials, environment: .production ) // Each call selects its database scope explicitly: let records = try await service.queryRecords( recordType: "Post", database: .public(.requires(.serverToServer)) )
To plug in a custom
TokenManager(e.g. with shared connection pooling), use thetokenManager:initializer instead:let pemString = try String(contentsOfFile: "private_key.pem", encoding: .utf8) let serverManager = try ServerToServerAuthManager( keyID: "your_key_id", pemString: pemString ) let service = CloudKitService( containerIdentifier: "iCloud.com.example.MyApp", tokenManager: serverManager, environment: .production )
MistKit provides comprehensive error handling with typed errors:
do {
let credentials = try Credentials(
apiAuth: APICredentials(apiToken: apiToken)
)
let service = CloudKitService(
containerIdentifier: "iCloud.com.example.MyApp",
credentials: credentials
)
// Perform operations — each call picks its database, e.g.:
let posts = try await service.queryRecords(
recordType: "Post",
database: .public(.prefers(.serverToServer))
)
} catch let error as CloudKitError {
print("CloudKit error: \\(error.localizedDescription)")
} catch let error as TokenManagerError {
print("Authentication error: \\(error.localizedDescription)")
} catch let error as CredentialsValidationError {
print("Credentials error: \\(error.localizedDescription)")
} catch {
print("Unexpected error: \\(error)")
}CloudKitError: CloudKit Web Services API errors (typed throws on every operation)CredentialsValidationError: Surfaces whenCredentials.initis called with neitherapiAuthnorserverToServerTokenManagerError: Authentication and credential errorsTokenStorageError: Token storage and persistence errors
Non-WASI platforms default to URLSessionTransport — no transport plumbing is
required. On Apple platforms, the default convenience initializer used in the
examples above wires up URLSessionTransport automatically.
WASI builds use the generic, transport-accepting initializer; see
Sources/MistKit/CloudKitService/CloudKitService+Initialization.swift for the
internal entry point. A custom transport on Apple platforms (e.g. for
server-side Swift with AsyncHTTPClient) is not yet exposed in the public
v1.0.0-beta surface — track via the project roadmap.
For applications that might upgrade from API-only to web authentication:
let adaptiveManager = AdaptiveTokenManager(
apiToken: apiToken,
storage: storage
)
// Later, upgrade to web authentication
try await adaptiveManager.upgradeToWebAuthentication(webAuthToken: webToken)Check out the Examples/ directory for complete working examples:
- MistDemo: Web-based CloudKit authentication demo with automatic token capture
- BushelCloud: Server-to-Server auth demo syncing macOS restore images, Xcode, and Swift versions — backend for the Bushel app
- CelestraCloud: RSS reader demonstrating CloudKit query filtering, sorting, and web etiquette patterns — backend for the Celestra app, built with SyndiKit
- API Documentation: Complete API reference
- CloudKit Web Services: Official CloudKit Web Services REST API documentation
- CloudKit framework: On-device CloudKit framework (iOS/macOS)
- CloudKit JS: Browser-based CloudKit access used for web auth token capture
- CKFetchWebAuthTokenOperation: iOS/macOS API for exchanging an iCloud session for a web auth token
- swift-openapi-generator: Generates type-safe Swift clients from OpenAPI specs
- swift-openapi-async-http-client: AsyncHTTPClient transport for OpenAPI clients
- AsyncHTTPClient: HTTP client for server-side Swift
- swift-crypto: Cross-platform crypto used for ECDSA P-256 server-to-server signing
MistKit is released under the MIT License. See LICENSE for details.
- Built on Swift OpenAPI Generator
- Uses Swift Crypto for server-to-server authentication
- Inspired by CloudKit Web Services REST API
- Composing Web Service Requests ✅
- Modifying Records (records/modify) ✅
- Fetching Records Using a Query (records/query) ✅
- Fetching Records by Record Name (records/lookup) ✅
- Fetching Current User Identity (users/caller) ✅
- Vapor Token Client ✅
- Vapor Token Storage ✅
- Vapor URL Client ✅
- Swift NIO URL Client ✅
- Date Field Types ✅
- Location Field Types ✅
- List Field Types ✅
- Reference Field Types ✅
- Error Codes ✅
- Fetching Zones (zones/list) ✅
- Uploading Assets (assets/upload) ✅
- Add Support for swiftlang/swift-source-compat-suite ✅
- MistDemo - Web Authentication Demo ✅
- WASM Platform Support ✅
- Android Platform Support ✅
- Name Component Types ✅
- Discovering User Identities (POST users/discover) ✅
- Fetching Record Changes (records/changes) ✅
- Fetching Zones by Identifier (zones/lookup) ✅
- Fetching Zone Changes (zones/changes) ✅
- Fix QueryFilter IN/NOT_IN serialization ✅
Querying & Sync
- Query pagination with continuation markers (#306) ✅
- Operation classification & batch sync result tracking (#296) ✅
Authentication
-
AuthenticationMiddlewarerefactor — eachAuthenticatorapplies itself (#294) ✅ - Strengthened environment & database configuration validation (#293) ✅
Error Handling
- Typed
TokenManagerErrorand safeRecordOperationconversion (#305) ✅ - Move
CloudKitResponseTypedefaults to protocol extension (#292) ✅
Concurrency
- Replace custom
AsyncChannelwithswift-async-algorithms(#280) ✅
MistDemo
-
--databaseflag anddemo-errorscommand (#282) ✅ - Test split, CRUD commands, auth fix, native app (#271 / #273) ✅
-
IntegrationTestRunnerrefactored into protocol-based phase pipeline (#283) ✅
Tooling & CI
- Test suite improvements (#286 / #287) ✅
- CI updates for May 2026 (#277) ✅
- Fail lint job when any command fails (#303) ✅
- Discovering All User Identities (GET users/discover)
- Referencing Existing Assets (assets/rereference)
- Fetching Contacts (users/lookup/contacts)
- Fetching Users by Email (users/lookup/email)
- Fetching Users by Record Name (users/lookup/id)
- Fetching Record Information (records/resolve)
- Accepting Share Records (records/accept)
- Fetching Database Changes (changes/database)
- Modifying Zones (zones/modify)
- Fetching Record Zone Changes (changes/zone)
- Fetching Subscriptions (subscriptions/list)
- Fetching Subscriptions by Identifier (subscriptions/lookup)
- Modifying Subscriptions (subscriptions/modify)
- Creating APNs Tokens (tokens/create)
- Registering Tokens (tokens/register)
- Feature: Add custom CloudKit zone support for queries
- System Field Integration
- Handle Data Size Limits
- Add architecture diagrams to Bushel documentation
- Add comprehensive test suite for Bushel demo
- Implement incremental sync with change tracking for Bushel
- Migrate Bushel Demo to it's own Repository
- Migrate Celestra Demo to it's own Repository
- Add CloudKit Schema Management APIs (cktool/cktooljs functionality)
- Add KeyPath-based QueryFilter API for Type-Safe Filtering
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: API Reference
MistKit: Bringing CloudKit to every Swift platform 🌟