atproto-kotlin
Code-generated AT Protocol Kotlin Multiplatform SDK
for Bluesky and atproto apps. Parses the upstream @atproto/lex lexicon
corpus at build time and emits idiomatic Kotlin: immutable data class
records, sealed-equivalent open unions with $type dispatch, typed value
classes for every string format, and suspend fun XRPC service interfaces
ready to drop into a Ktor client.

Modules
Codegen pipeline
The :generator module's seven stages, end to end:
flowchart LR
A[Lexicon JSON<br/>corpus] --> B[Parse<br/>→ IR]
B --> C[Resolve<br/>refs]
C --> D[Context-tag<br/>Mut / Read / Both]
D --> E[Name<br/>FqNames]
E --> F[Verify<br/>INV-1..4]
F --> G[Plan<br/>unions + splits]
G --> H[Emit<br/>KotlinPoet]
H --> I[:models<br/>generated source]
Each stage is a separate pass with its own tests. The verification pass
enforces invariants (no name collisions, no unresolved refs) before any
KotlinPoet emission, so generator errors fail fast with actionable
messages instead of producing broken Kotlin downstream.
Sample app
The Android reference consumer lives at samples/android/.
It authenticates via AT Protocol OAuth 2.0 (PAR + PKCE + DPoP) using
Chrome Custom Tabs and renders a feed from app.bsky.feed.getTimeline.
./gradlew :samples:android:installDebug
See samples/android/README.md for run
instructions and architecture details.
Getting started as a contributor
cd generator && npx lex install --ci && cd -
./gradlew build
Local prerequisites: JDK 17 (tracked by .java-version / .sdkmanrc),
Node 22+ (for npx lex install), and the Android SDK if you want to
build the sample. Spotless + ktlint run on every commit via pre-commit hooks
and on every push via the CI workflow.
Consuming the library
Every release is published to Maven Central (io.github.kikin81.atproto)
and simultaneously to GitHub Packages as a secondary channel. For
almost every consumer, Maven Central is what you want — no credentials,
no extra repository configuration.
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}
dependencies {
implementation("io.github.kikin81.atproto:runtime:<version>")
implementation("io.github.kikin81.atproto:models:<version>")
implementation("io.github.kikin81.atproto:oauth:<version>")
implementation("io.github.kikin81.atproto:compose:<version>")
implementation("io.github.kikin81.atproto:compose-material3:<version>")
}
Replace <version> with the
latest release
version. Artifacts are GPG-signed and include POM metadata, Gradle
Module Metadata, sources JARs, and Dokka-generated javadoc JARs.
iOS consumers: only JVM + metadata publications are cut from Linux
CI runners today. The Kotlin Multiplatform Gradle Module Metadata
describes the JVM target and allows iOS consumers to resolve the
runtime dependency graph, but the iOS klibs themselves aren't on
Maven Central yet. A macOS release runner will land in a follow-up.
GitHub Packages (pre-release / staging)
Every release is also pushed to GitHub Packages
as a secondary channel. This is mostly useful if you want to pick up
the exact same artifacts without routing through Maven Central's CDN,
or for early-access testing of unreleased builds. It requires
authentication with a GitHub PAT (read:packages scope) even for
public packages — a persistent GitHub Packages limitation. Most
consumers should stick with Maven Central.
maven {
url = uri("https://maven.pkg.github.com/kikin81/atproto-kotlin")
credentials {
username = System.getenv("GITHUB_ACTOR") ?: "<your-github-username>"
password = System.getenv("GITHUB_TOKEN") ?: "<your-pat-with-read-packages>"
}
}
Migrating from 4.x
5.0.0 renames every published artifactId to drop the redundant
at-protocol- prefix. The group (io.github.kikin81.atproto) is
unchanged. No consumer code changes are required — only the coordinate
strings in your build file.
implementation("io.github.kikin81.atproto:at-protocol-runtime:4.9.0")
implementation("io.github.kikin81.atproto:at-protocol-models:4.9.0")
implementation("io.github.kikin81.atproto:at-protocol-oauth:4.9.0")
implementation("io.github.kikin81.atproto:runtime:5.0.0")
implementation("io.github.kikin81.atproto:models:5.0.0")
implementation("io.github.kikin81.atproto:oauth:5.0.0")
The 4.x artifacts remain on Maven Central (they're immutable) but
receive no further updates. Pin to 5.0.0+ to pick up new releases.
Releases
Every push to main runs through .github/workflows/release.yaml, which
drives semantic-release via
open-turo/actions-jvm/release. The commit-analyzer reads
Conventional Commits and cuts a
version on / / :
feat: → minor bump (e.g. 1.1.2 → 1.2.0)
The release workflow runs gate jobs (lint, test, build) that
mirror CI, and only then runs the release job: semantic-release
analyzes commits, bumps , creates a git tag + GitHub
release, then runs via the
to upload artifacts to GitHub
Packages and Maven Central (via the
).
A final docs job rebuilds the Dokka API reference and pushes it to
GitHub Pages.
Central uploads auto-promote to repo1.maven.org — no manual step
needed. Artifacts are typically available within 15–30 minutes of a
release.
OpenSpec
This project uses OpenSpec-style
change proposals under openspec/. Active work lives under
openspec/changes/<name>/ with + +
+ ; archived changes land under
and their requirements are
promoted into permanent main specs at .
Run openspec list to see active + archived changes, or
openspec status --change <name> for artifact-level progress.
Contributing
PRs welcome. Read CONTRIBUTING.md for the quick-start
build, the per-module verification commands, and the Conventional Commits
expectations (since feat: / fix: on main cuts a release).
For non-trivial changes, open an issue first — GitHub auto-applies a
bug report or
feature request template
when you click New issue. Pull requests likewise pre-populate from
PULL_REQUEST_TEMPLATE.md — fill in
the affected-module checklist and the "How it was verified" section.
Larger architectural work goes through the
OpenSpec workflow described above; CONTRIBUTING.md
explains when that's required.
License
MIT © 2026 Francisco Velazquez. See LICENSE for the
full text.