brightdigit/mistkit
A Swift Package for Server-Side and Command-Line Access to CloudKit Web Services
Table of Contents
- Installation - Requirements - Platform Support - Quick Start
- Authentication - Error Handling - Advanced Usage - Examples
Overview
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.
Key Features
- π Cross-Platform Support: Works on macOS, iOS, tvOS, watchOS, visionOS, Linux, and Windows
- β‘ 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
Why Server-Side CloudKit?
Apple's CloudKit framework only runs on Apple platforms. MistKit wraps the CloudKit Web Services REST API so server-side Swift, Linux services, and command-line tools can take part in the same containers as your apps. Four patterns cover most uses:
- Public database as a managed catalog β a scheduled job writes data every user wants and the app just queries it. BushelCloud (
Examples/BushelCloud) syncs macOS restore images and Xcode/Swift versions for Bushel; CelestraCloud (Examples/CelestraCloud) syncs RSS feeds for Celestra. Software-version catalogs, asset packs, feature flags, and MDM configuration fit the same shape. - Private database on behalf of a user β the user signs in once, the server keeps their web auth token, and reads or writes their private database while they are away. HeartWitch links an Apple Watch to a Vapor backend this way; wearable data pipelines, two-way sync with external services, and server-side processing of uploads are the same idea.
- Web app β Apple device bridge β a browser portal for a CloudKit-backed app, or a webhook handler that writes straight into a user's records.
- Data aggregation β anonymized telemetry read through
records/changes, or crowdsourced data cleaned up by a background job.
The talk that walks through all of this is CloudKit as Your Backend below.
Getting Started
Installation
Add MistKit to your Package.swift:
dependencies: [
.package(url: "https://github.com/brightdigit/MistKit.git", from: "1.0.0-beta.5")
]Or add it through Xcode:
- File β Add Package Dependencies
- Enter:
https://github.com/brightdigit/MistKit.git - Select version and add to your target
Requirements
- Swift 6.1+
- Xcode 16.0+ (for iOS/macOS development)
- Linux: Ubuntu 18.04+ with Swift 6.1+
Platform Support
Minimum Platform Versions
| Platform | Minimum Version | |----------|-----------------| | macOS | 11.0+ | | iOS | 14.0+ | | tvOS | 14.0+ | | watchOS | 7.0+ | | visionOS | 1.0+ | | Linux | Ubuntu 18.04+ | | Windows | 10+ |
Quick Start
1. Choose Your Authentication Method
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.
API Token (read-only against the public database)
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
)Web Authentication (user-context routes, private/shared database)
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
)Server-to-Server (public database only)
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.
2. Call an Operation (database chosen per call)
let result = try await service.queryRecords(
Query(recordType: "Post"),
database: .public(.prefers(.serverToServer))
)
let records = result.recordsDatabase.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.
Usage
Authentication
API Token Authentication
- Get API Token:
- Log into the CloudKit Console - Navigate to CloudKit Database - Generate an API Token
- Set Environment Variable:
``bash export CLOUDKIT_API_TOKEN="your_api_token_here" ``
- Use in Code:
``swift 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
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
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:
```bash # 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):
```swift 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( Query(recordType: "Post"), database: .public(.requires(.serverToServer)) ).records ```
To plug in a custom TokenManager (e.g. with shared connection pooling), use the tokenManager: initializer instead:
``swift 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 ) ``
Error Handling
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(
Query(recordType: "Post"),
database: .public(.prefers(.serverToServer))
).records
} 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)")
}Error Types
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
Advanced Usage
More Operations
Beyond querying and CRUD, MistKit covers zones, subscriptions, push tokens, and asset re-referencing. Every call takes an explicit database:.
// Zones
let zone = try await service.createZone(
zoneName: "Notes",
database: .private
)
try await service.deleteZone(zoneName: "Notes", database: .private)
// Batch create/delete via service.modifyZones(_:database:)
// (takes [ZoneOperation], returns [ZoneChangeResult] β inspect
// `.zones` and `.failures` for per-zone outcomes).
// Subscriptions
let subs = try await service.listSubscriptions(database: .private)
let one = try await service.lookupSubscriptions(ids: ["sub-1"], database: .private)
// Create/update/delete via service.modifySubscriptions(_:database:)
// (takes [SubscriptionOperation], returns [SubscriptionResult]).
// APNs push tokens
let token = try await service.createAPNsToken(
environment: .development,
database: .private
)
try await service.registerAPNsToken(
token.apnsToken,
environment: .development,
database: .private
)
// Re-reference existing CDN assets without re-uploading bytes
let assets = try await service.rereferenceAssets(
[(recordName: "rec-1", fieldName: "photo")],
database: .private
)Change Tracking
CloudKit exposes four change-tracking endpoints. MistKit wraps all four; each single-request primitive has an auto-paginating fetchAll⦠companion.
| Apple endpoint | Purpose | MistKit method | Auto-paginating | |---|---|---|---| | records/changes | Fetching Record Changes | fetchRecordChanges | fetchAllRecordChanges | | changes/database | Fetching Database Changes β which zones changed | fetchDatabaseChanges | fetchAllDatabaseChanges | | changes/zone | Fetching Record Zone Changes β records within zones | fetchRecordZoneChanges | fetchAllRecordZoneChanges | | zones/changes | Fetching Zone Changes β deprecated by Apple | ~~fetchZoneChanges~~ | ~~fetchAllZoneChanges~~ |
zones/changesis deprecated by Apple in favor ofchanges/database, sofetchZoneChanges/fetchAllZoneChangesare marked@available(*, deprecated). UsefetchDatabaseChangesinstead.
The typical database-sync flow asks which zones changed, then fetches the records inside them:
// 1. Which zones changed?
let database = try await service.fetchDatabaseChanges(
syncToken: lastDatabaseToken,
database: .private
)
// 2. What changed inside them?
let result = try await service.fetchAllRecordZoneChanges(
zones: database.changedZones.map {
ZoneChangesRequest(zoneID: ZoneID(zoneName: $0.zoneName))
},
database: .private
)
for change in result.changes {
print("\(change.zone.zoneName): \(change.records.count) changed")
// Persist change.syncToken per zone β each zone paginates independently.
}Both operations report per-zone problems as data rather than throwing, so one bad zone never discards the zones that succeeded:
for failure in result.failures {
print("\(failure.zoneName) failed: \(failure.serverErrorCode.rawValue)")
}Auto-Chunking Conveniences
CloudKit caps batch requests at 200 items. lookupAllRecords and the lookupInfos: form of discoverAllUserIdentities split oversized inputs into β€maxRecordsPerRequest (200) batches automatically and concatenate the results in input order β no manual chunking required.
let records = try await service.lookupAllRecords(
recordNames: thousandsOfNames, // chunked into 200-item requests
database: .private
)
let identities = try await service.discoverAllUserIdentities(
lookupInfos: manyLookupInfos,
batchSize: 200
)HTTP Transport
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.
Adaptive Token Manager
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)Examples
Check out the Examples/ directory for complete working examples:
- MistDemo: Web-based CloudKit authentication demo with automatic token capture
- BushelCloud (standalone repo): Server-to-Server auth demo syncing macOS restore images, Xcode, and Swift versions from a scheduled GitHub Actions job β backend for the Bushel app
- CelestraCloud (standalone repo): RSS reader demonstrating CloudKit query filtering, sorting, and web etiquette patterns β backend for the Celestra app, built with SyndiKit
Documentation
- API Documentation: Complete API reference, hosted on Swift Package Index
Guides
The DocC catalog (Sources/MistKit/Documentation.docc/) carries the long-form guides. Links below point at the published pages; pages added on this branch appear once Swift Package Index rebuilds the default branch.
- CloudKit as Your Backend: the conference talk in article form β see below
- Authentication and Databases: which credentials reach which database, obtaining tokens and keys from the CloudKit Console
- Request Signing: token managers, authenticators, the middleware, and the ECDSA payload
- Working with Records: query, create, update, delete, batch, and sync
- Field Type Polymorphism: how nine CloudKit field types map onto one Swift enum, and the wire-format traps
- Handling Errors: typed errors at every layer and how CloudKit's JSON becomes a
CloudKitError - Deploying MistKit: static Linux builds, credentials in CI, scheduling, idempotency, observability
- Configuring MistKit and CloudKit Limits and Performance
- Abstraction Layer Architecture, OpenAPI Code Generation, Generated Code Workflow, Generated Code Analysis
- What CloudKit Got Wrong: where Apple's documentation and Apple's server disagree
- What the AI Got Wrong: an evidence-backed catalogue of AI-assisted development failure modes from this project
Articles on brightdigit.com: Rebuilding MistKit with Claude Code, part 1 and part 2.
CloudKit as Your Backend (talk)
From iOS to Server-Side Swift β given in 2026 at Swift Craft and iOSDevUK by Leo Dion (@leogdion@c.im). The full article, following the slide order with screenshots and code from this repository, is in the DocC catalog: CloudKit as Your Backend (source: CloudKitAsYourBackend.md). Download the slides: CloudKit-Backend-iOSDevUK.pdf (~12 MB).
CloudKit has excellent documentation for iOS and macOS client development. But backend services β podcast aggregation, RSS readers, data processing β face APIs that Apple barely documents. I rebuilt a comprehensive CloudKit library using AI-generated OpenAPI specifications. The result: type-safe Swift code supporting three authentication methods (server-to-server, web authentication token, and API token), typed error handling, and production deployments.
Links from the talk:
Leo Dion / BrightDigit
- MistKit on GitHub and MistKit issues β "What's next?"
- MistDemo β CLI, macOS app and web demo used for integration testing
- Bushel β Virtualization for App Developers
- AtLeast β Passive Timer for Apple Watch
- Heartwitch β Apple Watch heart-rate streaming; App Store
- BrightDigit
- linktr.ee/leogdion
- iOSDevUK
Use case examples
- BushelCloud β server-to-server sync of macOS restore images, Xcode and Swift versions for Bushel; the GitHub Actions deployment shown in the talk (also at
Examples/BushelCloud)
- cloudkit-sync-dev.yml β scheduled workflow - cloudkit-sync composite action
- CelestraCloud β RSS feed sync into a public database for Celestra, with query filtering and sorting (also at
Examples/CelestraCloud) - HeartWitch β the private-database, Apple Watch to Vapor bridge
- Rebuilding MistKit with Claude Code, part 1 and part 2
What is CloudKit
- WWDC 2014 "Introducing CloudKit" (session 208) β Apple no longer hosts the video; this mirror has the video, slides and transcript. Transcript only: ASCIIwwdc
- CloudKit framework documentation
- Enabling CloudKit in your app β Xcode capability setup
- CloudKit Console
- Integrating a text-based schema into your workflow
- cktool and Automating CloudKit Development
CloudKit Web Services
- CloudKit JS
- CloudKit Web Services Reference β archived
- Composing Web Service Requests - Accessing CloudKit Using an API Token - Accessing CloudKit Using a Server-to-Server Key - Document Revision History β last updated 2016 - Uploading Assets - Types and Dictionaries β field types - Error Codes - Discovering User Identities (POST users/discover) - Discovering All User Identities (GET users/discover) β the endpoint that returns HTTP 500
- Base URL:
https://api.apple-cloudkit.com/database/{version}/{container}/{environment}/{database}/{operation} - Apple Developer Support β contact URL in the OpenAPI document
Authentication
- CKFetchWebAuthTokenOperation
- Sign in with Apple β mentioned as not existing when Heartwitch was built
- swift-crypto β
P256.Signing.PrivateKeyused inRequestSignature - MistKit source shown in the talk:
AuthenticationMiddleware.swift,APITokenAuthenticator.swift,WebAuthTokenAuthenticator.swift,ServerToServerAuthenticator.swift,RequestSignature.swift
Swift OpenAPI Generator
- swift-openapi-generator
- WWDC23 "Meet Swift OpenAPI Generator"
- Documentation and tutorial: Working with Swift OpenAPI Generator
- ClientMiddleware β the "Implement a custom client middleware" bearer-token example is a section on that page
- Example projects β including auth, logging and retrying middleware examples
- Package ecosystem: swift-openapi-runtime, swift-openapi-urlsession, swift-openapi-async-http-client, swift-okhttp, swift-openapi-vapor, swift-openapi-hummingbird, swift-openapi-lambda
- OpenAPI Specification
- MistKit
openapi.yaml
Field types and error handling
- Broken call, CloudKit Web Services: Discovering All User Identities (GET users/discover) - Broken call, CloudKit JS: CloudKit.Container.discoverAllUserIdentities
- Apple Feedback FB22754466 β public copy on Open Radar; filed via Feedback Assistant
Deployment
- GitHub Actions: scheduled workflows (
schedule/ cron) - GitHub Actions: using secrets
- GitHub Actions: creating a composite action
- actions/checkout
- dawidd6/action-download-artifact
- Swift Docker images β the sync action builds with
swiftlang/swift:nightly-6.4.x-noble - swift-configuration β how the example CLIs read configuration from environment variables or arguments
- MistKitConfiguration β the shared credential-configuration package built on it (also at
Packages/MistKitConfiguration)
Other tools mentioned
- Hummingbird β server behind the MistDemo web interface
- Vapor β Heartwitch backend
- OBS Studio β Heartwitch streaming overlay
Apple References
- 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
Related Swift Packages
- 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
License
MistKit is released under the MIT License. See LICENSE for details.
Acknowledgments
- Built on Swift OpenAPI Generator
- Uses Swift Crypto for server-to-server authentication
- Inspired by CloudKit Web Services REST API
Roadmap
v1.1.0
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: API Reference
MistKit: Bringing CloudKit to every Swift platform π
Package Metadata
Repository: brightdigit/mistkit
Default branch: main
README: README.md