A lightweight, async/await-first Swift client for GitHub's Git Data API.
Built for apps that use GitHub as a file sync backend — notes apps, static site generators, config managers, or anything that needs to read/write files in a GitHub repo without depending on the git binary.
- 🚀 Modern Swift — async/await, Sendable, Swift 6 strict concurrency
- 🌳 Git Data API — trees, blobs, commits, refs (the building blocks of git)
- 📱 Cross-platform — iOS 16+, macOS 13+, visionOS 1+
- 🔐 Flexible auth — PAT, OAuth (
ASWebAuthenticationSession), or customAuthProvider - 🔄 Token refresh — automatic refresh for expiring GitHub App tokens
- 🪶 Zero dependencies — just Foundation + URLSession
// Package.swift
dependencies: [
.package(url: "https://github.com/mahopan/swift-github-api", from: "0.1.0"),
]
// Target
.target(dependencies: [
.product(name: "GitHubAPI", package: "swift-github-api"),
])import GitHubAPI
let client = GitHubClient(token: "ghp_your_token")
// List all files in a repo
let tree = try await client.trees.get(owner: "user", repo: "notes", sha: "HEAD", recursive: true)
for entry in tree.tree where entry.type == "blob" {
print(entry.path)
}
// Download a file
let blob = try await client.blobs.get(owner: "user", repo: "notes", sha: entry.sha!)
print(blob.decodedString ?? "binary")
// Push changes (create blob → tree → commit → update ref)
let newBlob = try await client.blobs.create(owner: "user", repo: "notes", request: .utf8("# Hello"))
let newTree = try await client.trees.create(owner: "user", repo: "notes", request: .init(
baseTree: tree.sha,
tree: [.blob(path: "hello.md", sha: newBlob.sha)]
))
let commit = try await client.commits.create(owner: "user", repo: "notes", request: .init(
message: "Add hello.md",
tree: newTree.sha,
parents: [currentCommitSha]
))
try await client.refs.update(owner: "user", repo: "notes", ref: "heads/main",
request: .init(sha: commit.sha))| Endpoint | Methods | Description |
|---|---|---|
client.repos |
get |
Repository info + permissions check |
client.trees |
get, create |
List/create Git tree objects |
client.blobs |
get, create |
Read/write file content |
client.commits |
get, create |
Read/create Git commits |
client.refs |
get, update, create |
Read/update branch pointers |
client.contents |
get |
Convenience for single file downloads |
import GitHubAPI
// 1. Configure OAuth (register at https://github.com/settings/developers)
let config = OAuthConfiguration(
clientId: "Iv1.your_client_id",
clientSecret: "your_secret",
redirectURI: "yourapp://github-callback",
scopes: ["repo"]
)
// 2. Run the flow (presents ASWebAuthenticationSession)
let flow = OAuthFlow(configuration: config)
let tokenResponse = try await flow.authenticate()
// 3. Use the token
let client = GitHubClient(token: tokenResponse.accessToken)
// Or with auto-refresh for expiring tokens:
let auth = OAuthAuth(
accessToken: tokenResponse.accessToken,
refreshToken: tokenResponse.refreshToken,
expiresAt: Date().addingTimeInterval(Double(tokenResponse.expiresIn ?? 0)),
configuration: config,
onTokenRefresh: { newToken in
// Store in Keychain
}
)
let client = GitHubClient(auth: auth)// Implement AuthProvider for custom flows (e.g., Keychain, OAuth refresh)
struct KeychainAuth: AuthProvider {
func token() async throws -> String {
try Keychain.get("github-token")
}
}
let client = GitHubClient(auth: KeychainAuth())let client = GitHubClient(
token: "ghp_...",
baseURL: URL(string: "https://github.example.com/api/v3")!
)| OctoKit.swift | swift-github-api | |
|---|---|---|
| API style | Callback-based | async/await native |
| Git Data API | Limited | First-class (core focus) |
| Scope | Full GitHub API | Git Data + auth only |
| Swift Concurrency | Partial | Full (Sendable, actor-safe) |
| Dependencies | RequestKit | None |
MIT — see LICENSE.