Athletic data has a portability problem. Every platform stores your activities in a proprietary format behind an OAuth wall. If the platform shuts down or changes its API terms, your data is effectively held hostage. ATProto — the protocol underlying Bluesky — offers a different model: data lives in a Personal Data Server (PDS) you control, and apps just read from it. pacelore has optional ATProto export wired into the ingest pipeline.
What ATProto actually is
ATProto is a federated protocol where each user has a PDS — either hosted by a provider (like Bluesky's own infrastructure) or self-hosted. The PDS is a repository of records identified by URIs like at://did:plc:xxx/com.pacelore.activity/yyy. Records are typed by lexicons — schemas that define the shape of the data.
Most people know ATProto through Bluesky's social features (posts, likes, follows). But the protocol is general-purpose. The lexicon system means you can store any structured data — including athletic activities — in a PDS alongside your social posts, all under your own identity.
The com.pacelore.activity lexicon
pacelore defines a custom lexicon for activity records. The structure:
{
"$type": "com.pacelore.activity",
"sport": "cycling",
"startTime": "2026-02-14T08:30:00Z",
"durationSec": 5400,
"distanceMeters": 72000,
"elevationGain": 820,
"avgHr": 148,
"avgWatts": 212,
"normalizedPower": 231,
"tss": 142,
"fitCid": "bafybeig...",
"source": "garmin",
"externalId": "12345678"
}
The fitCid field is an IPFS/Arweave content identifier pointing to the original FIT file. The record in the PDS is lightweight metadata; the binary file lives on content-addressed storage. If you want the raw data, follow the CID.
How the export works
ATProto export is opt-in per athlete. When enabled, the ingest pipeline adds a step after activity processing: call the ATProto PDS API to create a record in the athlete's repository.
await atpClient.com.atproto.repo.createRecord({
repo: athlete.atprotoDid,
collection: 'com.pacelore.activity',
record: buildActivityRecord(activity),
}); The athlete authenticates with their PDS (typically their Bluesky account) once during setup, and pacelore stores the session token. Every new activity automatically gets a PDS record. Historical activities can be backfilled with a single button press.
The portability argument
Once your activities are in your PDS, they're yours. Not in the sense of "we give you a data export button" — in the sense that the records literally live on infrastructure you control or can migrate. If pacelore shuts down tomorrow, your activity records remain in your PDS indefinitely. Any future app that speaks ATProto can read them.
Strava has a data export button. Garmin has a data export button. But those exports are one-time snapshots — you download a zip file, it lives on your laptop, and it's static. The ATProto records are live data that future applications can query, aggregate, and analyze without any migration step.
ATProto is not primarily a social protocol
This is the common misunderstanding. Bluesky is a social app built on ATProto. ATProto is a data protocol that happens to be used by Bluesky. The protocol has no inherent notion of followers, timelines, or social graphs — those are features the Bluesky app (the lexicons app.bsky.*) adds on top.
Using ATProto for athletic data doesn't mean posting your workouts as social media. The com.pacelore.activity records are private by default — visible only to apps you authorize. The PDS access control model is the same as any OAuth system. You can make records public if you want (for club visibility, for sharing a race result), but the default is private.
Status
ATProto export is in the settings panel under "Data Portability." It works with Bluesky-hosted PDSes (the most common case). Self-hosted PDS support requires manual configuration of the PDS endpoint — it works but isn't guided through the UI yet. Lexicon registry submission for com.pacelore.activity is pending.