Working with Mirrorball and Swift: What You Actually Need to Know
Mirrorball is a cloud-based motion capture platform, and the Swift SDK for it isn't something you just drop into a project and walk away from. It works, but the documentation is thin and the API changes enough between versions that older examples will break on you. I've spent a good chunk of time wrestling with it, and the most useful thing I can tell you is how to actually get data out of it and into a running app without losing your mind. You start by registering for a developer account at the Mirrorball dashboard. From there you create a new app and generate an App ID and secret. That's your credential pair for API calls. Add the Swift SDK to your project using Swift Package Manager, pointing to their GitHub repo URL. The package name in your Package.swift file is straightforward—look for the one they list in their README. Don't go hunting for alternatives on Sourcegraph; the one they maintain is the only one that works with their current API version. Once it's installed, you initialize the client with your credentials. Then you make a call to fetch your sessions or the tracking data within them. The response comes back as JSON that the SDK deserializes into model objects. That part is fine. The rough part is dealing with the tracking points themselves. Each frame contains hundreds of points labeled by joint name, and the coordinate format is in meters relative to a global origin point that your specific capture session defined. If you're trying to do anything beyond visualizing raw positions, you'll need to handle that coordinate transformation yourself.
Here's a realistic snippet of what the initialization looks like in practice:
let client = MirrorballClient(appId: "your-app-id", appSecret: "your-secret")
client.authenticate { result in
switch result {
case .success(let token):
print("Authenticated successfully")
case .failure(let error):
print("Auth failed: \(error.localizedDescription)")
}
}
Authentication returns a bearer token you carry through subsequent requests. That token expires after a set period, usually around an hour, and you'll get a 401 on any call after that if you don't refresh. The SDK has a refresh method but it's not automatic. I built a small extension that intercepts 401 responses and retries the call after refreshing the token. Without that, you're manually tracking token lifetime and re-authenticating mid-stream, which breaks any longer batch processing jobs you might run overnight. The biggest issue I ran into was the sheer volume of data. A single 60-second capture at 120fps with full body tracking generates roughly 7,200 frames, each with maybe 50 to 80 labeled joints. That's a lot of points to deserialize and process in memory, especially if you're running this on a device rather than a server. My first attempt at pulling and processing a session crashed the app on anything less than an M-series Mac. I ended up switching to a streaming approach where I fetch frames in batches of 30 and process them as they arrive instead of loading everything at once. Memory usage dropped from about 400MB to under 80MB for the same session. Another thing that caught me off guard: the joint naming convention. Mirrorball uses its own labels that don't always match standard motion capture rigs. The SDK maps them internally, but the mapping isn't always consistent across different capture setups. If you're building analysis tools that depend on specific joints being present, always add a fallback check. I wrote a small helper that verifies whether a requested joint label actually exists in the current frame's data array before trying to access it. It sounds obvious in retrospect, but missing joints caused silent failures in my analysis pipeline that I spent two days tracking down because the data structure just had an empty array where I expected values.
Get the Full Details

For filtering and querying, the API supports basic parameter filters like session date ranges and subject identifiers. It does not support complex spatial queries or time-range interpolation on the server side. If you need to filter frames where a particular joint exceeded a certain threshold, you're doing that in your own code after fetching the raw data. There's no shortcut there. I ended up writing a small extension that wraps the frame collection in a sequence that lets me chain filters the way I actually think about the data:
extension Sequence where Element == Frame {
func whereJoint(_ jointName: String, satisfies predicate: (Vector3) -> Bool) -> [Frame] {
filter { frame in
guard let point = frame.points[jointName] else { return false }
return predicate(point.position)
}
}
}
This kind of chaining saves you from writing nested loops everywhere you need conditional filtering. It also makes the code readable when you're trying to explain to someone else what your analysis is actually doing. SDK version mismatches are the most common source of confusion. The Mirrorball team pushes updates to their API occasionally, and older SDK versions will compile fine but fail at runtime with cryptic errors about missing fields in the response. Always pin your package to a specific version and check their release notes before upgrading. Breaking changes do happen. Rate limiting is another thing you'll hit if you're processing large numbers of sessions. The API allows a certain number of requests per minute, and if you're pulling multiple sessions in a loop, you'll get throttled. I added a simple rate limiter using a semaphore with a one-second window. It's not elegant but it keeps your calls flowing without hitting the limit:
private let rateLimiter = DispatchSemaphore(value: 1) func fetchSession(id: String, completion: @escaping (Result) -> Void) { rateLimiter.wait() client.fetchSession(id: id) { result in completion(result) DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) { self.rateLimiter.signal() } } }
Don't skip the rate limiter. I learned that the hard way when my test script started getting intermittent 429 errors and I wasted half a day wondering if my authentication was broken. Mirrorball's Swift SDK is solid for basic data retrieval and visualization, but it's not a full analysis toolkit. There's no built-in kinematic chain calculation, no inverse dynamics solver, no gait analysis module. If you need those, you're bringing your own math. I've seen people assume the SDK handles joint angle computation out of the box—it doesn't. You calculate angles between vectors formed by three consecutive joints yourself. It's not difficult, but it's extra work that isn't mentioned in the getting-started docs. The platform also doesn't support real-time streaming from active capture. Everything is pull-based. If you're building a system that needs live feedback during a capture session, you're out of luck with this SDK. You'd need to pair it with a custom WebSocket listener or use Mirrorball's separate real-time API if it's available for your tier. I checked, and the real-time endpoint isn't included in the standard developer tier, so unless you're on an enterprise plan, you're working with recorded sessions only.

For projects that need real-time data or advanced biomechanical calculations, you might be better off looking at alternative platforms like Vicon's workspace or even processing raw marker data directly if you have the hardware. Mirrorball sits in a middle ground that works well for post-hoc analysis of captured sessions but falls short if your use case demands live processing or deep kinematic modeling. The download link for the SDK is on their official GitHub repository. Clone it, run the example project first, and read through their API reference before trying to build something on top of it. The example project shows you the correct authentication flow and how to navigate the response models, which saves you from reverse-engineering the JSON structure. I wish I'd done that before spending an afternoon figuring out why my joint position values were coming back as zero—they were coming back correctly, I just hadn't realized the coordinate system was relative to the capture rig's origin point and I needed to apply a transform offset I'd overlooked in the session metadata.