Requires Xcode 26 or newer. The Swift Package targets iOS, macOS, Mac Catalyst, tvOS, and watchOS 26.
Install with Swift Package Manager in Xcode using:
https://github.com/putdotio/putio-sdk-swift.git
Or add it to Package.swift and depend on the PutioSDK product:
dependencies: [
.package(url: "https://github.com/putdotio/putio-sdk-swift.git", from: "4.0.0")
]CocoaPods is no longer supported: trunk goes read-only in December 2026, and 3.8.1 is the last PutioSDK pod. Switch to Swift Package Manager for 4.0.0 and later.
import PutioSDK
let sdk = PutioSDK(
config: PutioSDKConfig(
clientID: "<your-client-id>",
token: "<your-access-token>"
)
)
Task {
do {
let account = try await sdk.getAccountInfo()
print(account.username)
} catch let error as PutioSDKError {
print(error.message)
print(error.recoverySuggestion ?? "")
}
}Every network call is async throws over native URLSession; there is no third-party networking dependency. URL builders and callback parsing are synchronous.
Apps that need a custom transport for tests, fixtures, or specialized session configuration can pass their own URLSession:
let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [MockURLProtocol.self]
let sdk = PutioSDK(
config: PutioSDKConfig(clientID: "<your-client-id>"),
urlSession: URLSession(configuration: configuration)
)Thrown SDK errors are PutioSDKError values that conform to LocalizedError and expose small classification helpers for app code:
do {
_ = try await sdk.getFile(fileID: 42)
} catch let error as PutioSDKError {
if error.isAuthenticationFailure {
// refresh credentials or send the user through sign-in
} else if error.isRetryable {
// schedule a retry with backoff
} else if error.matches(statusCode: 404) {
// refresh stale local state
}
}Media URLs carry the account's download token, never the OAuth token. The download token only authorizes file media endpoints such as streams, HLS, downloads, and subtitles, so it is the credential to hand to players, cast receivers, and external apps. Fetch it once from account info:
let account = try await sdk.getAccountInfo(query: PutioAccountInfoQuery(downloadToken: true))
let downloadToken = account.downloadTokenThe SDK resolves video metadata with the configured OAuth token and builds the HLS URL with the download token:
switch try await sdk.resolveVideoPlaybackSource(fileID: 42, downloadToken: downloadToken) {
case .ready(let source):
play(url: source.url, startingAt: source.startFrom)
case .conversionRequired:
showConversionRequired()
}Passing a non-video file throws PutioVideoPlaybackResolutionError.unsupportedFileType with a
localized recovery suggestion.
Audio files resolve the same way into a direct stream source, with startFrom carrying the saved
position:
let source = try await sdk.resolveAudioPlaybackSource(fileID: 50, downloadToken: downloadToken)
play(url: source.url, startingAt: source.startFrom)Passing a non-audio file throws PutioAudioPlaybackResolutionError.unsupportedFileType.
PutioFile and PutioNextFile build the same URLs directly with getStreamURL(downloadToken:),
getHlsStreamURL(downloadToken:), getAudioStreamURL(downloadToken:),
getDownloadURL(downloadToken:), and getMp4DownloadURL(downloadToken:).
Media URLs are still bearer credentials for the account's files. Use them for playback and downloads; do not log or persist them.
/config stores whatever keys an app writes. Declare the shape in the app and let the SDK carry it:
// The SDK decodes off the caller's actor; `nonisolated` keeps the conformance
// usable there in targets with `MainActor` default isolation.
nonisolated struct AppConfig: Decodable {
var autoplayNextVideo: Bool
enum CodingKeys: String, CodingKey {
case autoplayNextVideo = "autoplay_next_video"
}
// The document only holds keys some client has written; missing keys are
// the app's defaults, not decoding failures.
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
autoplayNextVideo = try container.decodeIfPresent(Bool.self, forKey: .autoplayNextVideo) ?? false
}
}
let config = try await sdk.getConfig(as: AppConfig.self)
_ = try await sdk.setConfigValue(key: "autoplay_next_video", true)The example app shows a minimal ASWebAuthenticationSession flow with your own client ID and redirect URI, followed by an account fetch:
Generate a state with try PutioSDK.generateOAuthState(), pass it to getAuthURL(redirectURI:state:), then extract the token with accessToken(fromOAuthCallback:expectedScheme:expectedHost:expectedState:), which rejects callbacks whose state does not match.
- Contributing for setup,
make verify, live API checks, and releases - Architecture for the transport, concurrency posture, and covered API surface
- Security policy for private vulnerability reports; fixes target the latest published release and
main - Agent guide
This project is available under the MIT License
