Cross-platform Swift library for the Mastodon API
TootSDK is a framework for Mastodon and the Fediverse, for iOS, macOS and other Swift platforms. It provides a toolkit for authorizing a user with an instance, and interacting with their posts.
TootSDK is a community developed SDK for Mastodon and the Fediverse. It is designed to work with all major servers (Mastodon, Pleroma, PixelFed etc).
You can use TootSDK to build a client for Apple operating systems, or Linux with Vapor.
When app developers build apps for Mastodon and the Fediverse, every developer ends up having to solve the same set of problems when it comes to the API and data model.
Konstantin and Dave decided to share this effort.
TootSDK is a shared Swift Package that any client app can be built on.
- Async/Await based. All asynchronous functions are defined as Async ones that you can use with Async/Await code (Note: full concurrency support is coming with Swift 6.2)
- Internal consistency and standardization of model property names
- Standardization across all supported Fediverse APIs
- Multi-server support with automatic flavour detection and version-aware feature handling
- Platform agnostic (TootSDK shouldn't care if it's on iOS, macOS or Linux!)
Please don't hesitate to open an issue or create a PR for features you need π
It's easy to get started with TootSDK.
- Add TootSDK to your project via Swift Package Manager:
https://github.com/TootSDK/TootSDK
. (also available on Codeberg:https://codeberg.org/TootSDK/TootSDK
):
.package(url: "https://github.com/TootSDK/TootSDK.git", from: "18.2.0")
- Instantiate with an instanceURL and accessToken:
let instanceURL = URL(string: "social.yourinstance.com")
let client = try await TootClient(connect: instanceURL, accessToken: "USERACCESSTOKEN")
The connect
initializer automatically detects the server type (Mastodon, Pleroma, Pixelfed, etc.) and version, enabling TootSDK to adapt its behavior for optimal compatibility.
Network Sandbox Capability/Entitlement (macOS)
When using TootSDK within a macOS target you will need to enable the com.apple.security.network.client
entitlement in your entitlements file or within the Signing & Capabilities tab in Xcode.
<key>com.apple.security.network.client</key>
<true/>
- Instantiate your client and obtain access token:
let client = try await TootClient(connect: url)
- Use the sign in helper we've created based on
ASWebAuthenticationSession
:
let client = try await TootClient(connect: url)
guard let accessToken = try await client.presentSignIn(callbackURI: callbackURI) else {
// handle failed sign in
return
}
That's it π!
We recommend keeping the accessToken somewhere secure, for example the Keychain.
Check out our tiny app example too.
- Instantiate your client without a token:
let client = try await TootClient(connect: instanceURL)
- Retrieve an authorization URL to present to the user (so they can sign in)
let authURL = client.createAuthorizeURL(callbackURI: "myapp://someurl")
- Present the the authorization URL as a web page
- Let the user sign in, and wait for the callbackURI to be called
- When that callbackURI is called, give it back to the client to collect the token
let accessToken = client.collectToken(returnUrl: url, callbackURI: callbackURI)
We recommend keeping the accessToken somewhere secure, for example the Keychain.
Once you have your client connected, you're going to want to use it. Our example apps and reference docs will help you get into the nitty gritty of it all, but some key concepts are highlighted here.
Accessing a user's timeline
There are several different types of timeline in TootSDK that you can access, for example their home timeline, the local timeline of their instance, or the federated timeline. These are all enumerated in the Timeline
enum.
You can retrieve the latest posts (up to 40 on Mastodon) with a call like so:
let items = try await client.getTimeline(.home)
let posts = items.result
TootSDK returns Posts, Accounts, Lists and DomainBblocks as PagedResult
. In our code, items
is a PagedResult struct. It contains a property called result
which will be the type of data request (in this case an array of Post
).
Formatting posts and user handles
Fediverse servers support rich content formatting for posts and user handles, as well as custom emojis which can be defined per instance. In TootSDK we handle this using renderers. You can create your own to fit your use case or you can use one ours:
-
AttributedStringRenderer
- convers the content to an attributes string -
UniversalRenderer
- removes all formatting and attempts to display the content as plain text
We also have AppKitAttribStringRenderer
/UIKitAttribStringRenderer
which show how to incorporate custom emojis.
Paging requests
Some requests in TootSDK allow pagination in order to request more information. TootSDK can request a specific page using the PagedInfo
struct and handles paginaged server responses using the PagedResult
struct.
PagedInfo has the following properties:
- maxId (Return results older than ID)
- minId (Return results immediately newer than ID)
- sinceId (Return results newer than ID)
So for example, if we want all posts from the user's home timeline that are newer than post ID 100, we could write:
let items = try await client.getTimeline(.home, PagedInfo(minId: 100))
let posts = items.result
Paged requests also deliver a PagedInfo struct as a property of the PagedResult
returned, which means you can use that for subsequent requests of the same type.
var pagedInfo: PagedInfo?
var posts: [Post] = []
func retrievePosts() async {
let items = try await client.getTimeline(.home, pagedInfo)
posts.append(contentsOf: items.result)
self.pagedInfo = items.pagedInfo
}
TootSDK implements several facilities to make it easier to iterate over multiple pages using the hasPrevious
, hasNext
, previousPage
and nextPage
properties of PagedResult
:
var pagedInfo: PagedInfo? = nil
var hasMore = true
let query = TootNotificationParams(types: [.mention])
while hasMore {
let page = try await client.getNotifications(params: query, pagedInfo)
for notification in page.result {
print(notification.id)
}
hasMore = page.hasPrevious
pagedInfo = page.previousPage
}
hasPrevious
or hasNext
can correctly interpret the server response in all cases.
You can learn more about how pagination works for Fediverse servers using a Mastodon compatible API here.
Streaming timelines
In TootSDK 4.0, experimental support for streaming timelines was introduced. It allows an app to subscribe for one or more available timelines in order to receive events as they happen instead of polling the server.
// open a socket to a specific timeline
let stream = try! await client.streaming.subscribe(to: .userNotification)
do {
// listen for events
for try await event in stream {
print("got event")
switch event {
case .connectionUp:
//...
case .connectionDown:
//...
case .receivedEvent(let eventContent):
//...
}
}
} catch {
print(String(describing: error))
}
An example of subscribing to a timeline is available in StreamEvents
You can learn more about Streaminng event support for Mastodon here.
You can learn more about Pleroma's implementation of streaming here.
Creating an account
-
Register the app with the following scopes
["read", "write:accounts"]
. -
Get instance information and determine the sign up requirements. Some instances may not be open for registration while others may require additional verification.
let instance = try await client.getInstanceInfo()
if instance.registrations == false {
// instance not open for registration
return
}
// ...
- Use the
registerAccount
method to create a user account:
let params = RegisterAccountParams(
username: name, email: email, password: password, agreement: true, locale: "en")
let token = try await client.registerAccount(params: params)
TootSDK supports multiple Fediverse server implementations and automatically adapts to their specific APIs and capabilities.
TootSDK automatically detects and supports the following server types:
- Mastodon - The original and most widely used server
- Pleroma - Lightweight alternative implementation
- Akkoma - Fork of Pleroma with additional features
- Pixelfed - Instagram-like photo sharing platform
- Friendica - Facebook-like social platform
- GoToSocial - Lightweight ActivityPub server
- Firefish (formerly Calckey) - Feature-rich Misskey fork
- Catodon - Another Misskey variant
- Iceshrimp - Firefish fork focused on stability
- Sharkey - Misskey fork with additional features
When you connect to a server, TootSDK automatically:
- Detects the server type (flavour)
- Parses the server version
- Adapts API calls for optimal compatibility
let client = try await TootClient(connect: instanceURL)
print("Connected to \(client.flavour) server")
print("Version: \(client.versionString ?? "unknown")")
Not all features are available on all servers or server versions. TootSDK provides a feature detection system:
// Check if a feature is supported
if client.supportsFeature(.deleteMedia) {
try await client.deleteMedia(id: mediaId)
} else {
print("This server doesn't support deleting media")
}
// Features automatically check version requirements
// For example, deleteMedia requires Mastodon API v4+
Many Fediverse servers (like Akkoma, Pleroma, and others) implement the Mastodon API alongside their own native APIs. TootSDK automatically handles this cross-flavour compatibility:
// A feature requiring Mastodon API v4+
let feature = TootFeature(requirements: [
.from(.mastodon, version: 4)
])
// This works on any server that reports Mastodon API compatibility,
// even if it's not a Mastodon server:
// - Akkoma server with { "mastodon": 6 } β Supported
// - Pleroma server with { "mastodon": 4 } β Supported
// - Firefish server with { "mastodon": 3 } β Not supported
When a server reports support for the Mastodon API (through the InstanceV2 apiVersions
field), TootSDK will check Mastodon API requirements against that version, regardless of the server's actual flavour. This ensures maximum compatibility across the Fediverse.
Some features require specific minimum versions. TootSDK supports two types of version checking:
For servers that support the InstanceV2 API (like modern Mastodon), TootSDK can check against the API version rather than the display version. This is more reliable as API versions are standardized:
// Define a feature requiring Mastodon API v6 or higher
let feature = TootFeature(requirements: [
.from(.mastodon, version: 6) // Requires API version 6+
])
// With version ranges
let rangedFeature = TootFeature(requirements: [
.from(.mastodon, version: 3, to: 5) // API versions 3-5
])
// Maximum version constraint
let deprecatedFeature = TootFeature(requirements: [
.until(.mastodon, version: 3) // Only API versions up to 3
])
For servers that don't provide API versions or when you need to check against the server's display version:
// Define a feature requiring specific display versions
let feature = TootFeature(requirements: [
.from(.mastodon, displayVersion: "4.4.0"),
.from(.pleroma, displayVersion: "2.5.0")
])
// With version ranges
let rangedFeature = TootFeature(requirements: [
.from(.pixelfed, displayVersion: "2.0.0", to: "3.0.0")
])
// Maximum version constraint
let legacyFeature = TootFeature(requirements: [
.until(.akkoma, displayVersion: "3.0.0")
])
Please note that display version parsing is not always reliable (especially for servers that return a complex compatibility string).
For very unique circumstances, you can specify both API version and display version fallback:
let feature = TootFeature(requirements: [
// Prefer API version 4, fallback to display version 4.4.0
.from(.mastodon, version: 4, fallbackDisplayVersion: "4.4.0")
])
This will:
- Check API version if available (for InstanceV2 servers)
- Fall back to display version if API version is not available
- Fail if neither requirement is met
TootSDK handles various version string formats used by different servers:
- Standard semantic versions:
"4.2.0"
- Pre-release versions:
"4.4.0-rc1"
- Compatibility strings:
"2.7.2 (compatible; Pixelfed 0.11.4)"
- Complex formats:
"3.5.3+glitch"
The SDK extracts and parses version numbers if possible, or falling back to regex patterns when needed.
You can define custom features with specific server and version requirements:
// Feature only for specific servers (any version)
let customFeature = TootFeature(supportedFlavours: [.mastodon, .pleroma])
// Feature with mixed version requirements
let versionedFeature = TootFeature(requirements: [
.from(.mastodon, version: 4), // Mastodon API v4+
.from(.pleroma, displayVersion: "2.5"), // Pleroma 2.5+ (display version)
.any(.akkoma) // Any Akkoma version
])
// Feature supported by ALL servers, with version requirements for some
let universalFeature = TootFeature(allExcept: [
.from(.mastodon, version: 3), // Mastodon needs API v3+
.from(.pleroma, displayVersion: "2.0.0") // Pleroma needs 2.0+
// All other servers support any version
])
// Feature supported by specific servers, with version requirements for some
let selectiveFeature = TootFeature(
anyVersion: [.friendica, .akkoma], // Any version of these flavours
requirements: [
.from(.mastodon, displayVersion: "3.5.0"), // Mastodon needs 3.5+
.from(.pixelfed, displayVersion: "2.0.0") // Pixelfed needs 2.0+
]
)
// Check if current server supports it
if client.supportsFeature(customFeature) {
// Use the feature
}
Saving and Restoring Server Configuration
In some situations, it may be helpful for your app to cache TootClient
and restore it without having to repeatedly call connect()
:
// Save server configuration e.g. encoding it as JSON:
let config = client.serverConfiguration
let data = try JSONEncoder().encode(config)
// Store data in UserDefaults, Keychain, etc.
// β οΈ serverConfiguration does NOT include secrets, only version and flavour information of the server
// Restore server configuration
let savedConfig = try JSONDecoder().decode(ServerConfiguration.self, from: data)
let client = TootClient(
instanceURL: instanceURL,
accessToken: accessToken,
serverConfiguration: savedConfig
)
- Reference documentation is available here
- Examples:
- swiftyadmin - a command line utility to interact with and control a server using TootSDK
- tootsdk-release - Example GitHub action to publish a post when a new release is published.
- Our guide to contributing is available here: CONTRIBUTING.md.
- All contributions, pull requests, and issues are expected to adhere to our community code of conduct: CODE_OF_CONDUCT.md.
TootSDK is licensed with the BSD-3-Clause license, more information here: LICENSE.MD
This is a permissive license which allows for any type of use, provided the copyright notice is included.
- The Mastodon API documentation https://github.com/mastodon/documentation
- We hat-tip top Metatext's source for some guidance on what's where: https://github.com/metabolist/metatext
- Kris Slazinski for our TootSDK logo π€©