Swift SDK
The official Skytells SDK for Swift — access Skytells AI services from iOS, macOS, tvOS, and watchOS.
This SDK is built with Swift concurrency (async/await) and supports iOS 15.0+, macOS 12.0+, tvOS 15.0+, and watchOS 8.0+.
Requirements
The Skytells Swift SDK requires:
- iOS 15.0+ / macOS 12.0+ / tvOS 15.0+ / watchOS 8.0+
- Swift 5.9+
- Xcode 15.0+
Installation
Adding to your project
The Skytells Swift SDK is distributed as a Swift Package and can be added via Xcode or Swift Package Manager.
After installation, import the Skytells module into your project and start making API calls with just a few lines of code.
1. In Xcode, go to File → Add Package Dependencies…
2. Enter the repository URL:
https://github.com/Skytells/swift-sdk.git
3. Set the dependency rule to "Up to Next Major Version"
starting from 1.0.0.
4. Select the "Skytells" library product and add it
to your target.
Quick Start
Getting Started with Skytells
The SDK provides a simple and intuitive interface for interacting with the Skytells API. The main entry point is SkytellsClient, which you initialize with your API key.
The example on the right demonstrates the basic workflow:
- Initialize the client with your API key
- Make a prediction using a model
- Process the results
All API methods are async and use Swift concurrency, so you can call them with try await in any async context.
import Skytells
// Initialize with your API key
let client = SkytellsClient(apiKey: "sk-your-api-key")
// Or use the factory method
let client = Skytells.createClient(apiKey: "sk-your-api-key")
Complete Example
import Skytells
// Initialize the client
let client = SkytellsClient(apiKey: "sk-your-api-key")
// List available models
let models = try await client.listModels()
print("Available models:", models)
// Make a prediction
let prediction = try await client.predict(.init(
model: "vendor/model-name",
input: ["prompt": "Generate a creative story about AI"],
await: true
))
// Access the result
if let text = prediction.outputString {
print("Output:", text)
}
Authentication
Setting Up Authentication
To access the Skytells API, you need an API key from your Skytells dashboard. This key authenticates your requests and determines your access level to different features and models.
Never embed your API key directly in client-side code that ships to users. Store it securely using environment variables, Keychain, or a backend proxy.
import Skytells
// With API key
let client = SkytellsClient(apiKey: "sk-your-api-key")
API Reference
Available Methods
The SkytellsClient provides the following methods for interacting with the Skytells API.
Predictions
predict(_:)— Run a prediction on a modelgetPrediction(id:)— Get a prediction by IDcancelPrediction(id:)— Cancel a running predictiondeletePrediction(id:)— Delete a prediction
Models
listModels()— List all available models
// Run a prediction
let prediction = try await client.predict(.init(
model: "vendor/model-name",
input: ["prompt": "Your prompt here"],
await: true
))
Error Handling
Handling Errors
The SDK throws SkytellsError for API errors, which includes the error message, error ID, and HTTP status code. Use Swift's do-catch to handle errors gracefully.
Network errors and other system-level errors are thrown as standard Swift errors.
do {
let prediction = try await client.predict(.init(
model: "vendor/model",
input: ["prompt": "test"]
))
} catch let error as SkytellsError {
print("Error: \(error.message)")
print("Error ID: \(error.errorId)")
print("HTTP Status: \(error.httpStatus)")
} catch {
print("Unexpected error: \(error)")
}
Best Practices
Avoiding Memory Leaks & Retain Cycles
When using the SDK inside classes (e.g. view models, coordinators), capturing self strongly in async closures or Task blocks can create retain cycles that prevent deallocation.
Always use [weak self] when capturing self in closures or Task blocks, and guard against nil before proceeding.
A common pitfall is storing the client as a property and capturing self in a Task — if the task outlives the object, you'll leak memory.
class PredictionViewModel: ObservableObject {
private let client = SkytellsClient(
apiKey: "sk-your-api-key"
)
@Published var output: String = ""
@Published var isLoading = false
func generate(prompt: String) {
isLoading = true
Task { [weak self] in
guard let self else { return }
do {
let prediction = try await self.client
.predict(.init(
model: "vendor/model",
input: ["prompt": prompt],
await: true
))
await MainActor.run {
self.output = prediction
.outputString ?? ""
self.isLoading = false
}
} catch {
await MainActor.run {
self.isLoading = false
}
}
}
}
}
Client Lifecycle
Create a single SkytellsClient instance and reuse it across your app rather than creating a new one for each request. This avoids unnecessary allocations and lets the underlying URLSession reuse connections.
For SwiftUI apps, inject the client through the environment or a shared singleton.
// A shared client instance for the lifetime of the app
extension SkytellsClient {
static let shared = SkytellsClient(
apiKey: Config.apiKey
)
}
// Usage anywhere in your app
let prediction = try await SkytellsClient.shared
.predict(.init(
model: "vendor/model",
input: ["prompt": "hello"]
))
Concurrency Tips
Always update UI on the main actor. The SDK's network calls run on a background executor, so you must dispatch back to MainActor before mutating any @Published or @State properties.
Use structured concurrency (async let, TaskGroup) when making multiple independent API calls to run them in parallel.
// Run multiple predictions in parallel
async let photo = client.predict(.init(
model: "vendor/image-model",
input: ["prompt": "A mountain landscape"],
await: true
))
async let text = client.predict(.init(
model: "vendor/text-model",
input: ["prompt": "Describe a mountain"],
await: true
))
// Await both results
let (photoResult, textResult) = try await (
photo, text
)
DO ✅
// ✅ DO: Capture self weakly in Task blocks
Task { [weak self] in
guard let self else { return }
let result = try await self.client.predict(.init(
model: "vendor/model",
input: ["prompt": "hello"],
await: true
))
await MainActor.run {
self.output = result.outputString ?? ""
}
}
DON'T ❌
The following patterns cause memory leaks, retain cycles, or undefined behavior. Avoid them.
// ❌ DON'T: Capture self strongly — causes retain cycles
Task {
let result = try await self.client.predict(.init(
model: "vendor/model",
input: ["prompt": "hello"],
await: true
))
self.output = result.outputString ?? ""
}
Webhooks
Predictions with Webhooks
When running long-running predictions, you can attach a webhook URL so Skytells notifies your server when the prediction completes, fails, or is cancelled — instead of polling.
Pass a webhook dictionary in your prediction input with:
url— your HTTPS endpointevents— an array of event types:completed,failed,canceled
Your webhook endpoint must be publicly accessible over HTTPS. Skytells sends a POST request with the prediction payload when the event fires.
import Skytells
let client = SkytellsClient(apiKey: "sk-your-api-key")
// Run a prediction with a webhook callback
let prediction = try await client.predict(.init(
model: "vendor/image-model",
input: ["prompt": "A futuristic cityscape"],
webhook: [
"url": "https://your-server.com/webhook",
"events": ["completed", "failed"]
]
))
// The prediction starts asynchronously.
// Skytells will POST to your webhook URL when done.
print("Prediction ID:", prediction.id)
Handling Webhook Payloads
When Skytells triggers a webhook, it sends a POST request with the full prediction object as JSON. Here's how to handle it on a Swift server (e.g. using Vapor).
The webhook payload matches the same Prediction structure returned by the SDK.
import Vapor
// Define the webhook payload structure
struct WebhookPayload: Content {
let id: String
let status: String
let model: String
let output: AnyCodable?
let error: String?
}
func routes(_ app: Application) throws {
app.post("webhook") { req -> HTTPStatus in
let payload = try req.content.decode(
WebhookPayload.self
)
switch payload.status {
case "succeeded":
// Handle successful prediction
print("Prediction \(payload.id) succeeded")
print("Output: \(payload.output ?? "")")
case "failed":
// Handle failure
print("Prediction \(payload.id) failed")
print("Error: \(payload.error ?? "")")
case "canceled":
// Handle cancellation
print("Prediction \(payload.id) canceled")
default:
break
}
return .ok
}
}