···77bskyKit implements the `app.bsky.*` lexicons for the AT Protocol, giving you everything needed to build a full-featured Bluesky client:
8899- **Read Operations** - Fetch timelines, profiles, posts, threads, notifications, and social graphs
1010-- **Write Operations** - Create posts, likes, reposts, follows, and blocks
1010+- **Write Operations** - Create posts, likes, reposts, follows, blocks, and low-level repo writes
1111- **Rich Text** - Automatic detection and creation of mentions, links, and hashtags with proper byte indexing
1212-- **Type Safety** - Fully typed models for all API responses with `Codable` and `Sendable` conformance
1212+- **Type Safety** - Typed models for stable endpoints plus `JSONValue` for rapidly evolving/unspecced payloads
1313- **SwiftUI Ready** - Models conform to `Identifiable` for seamless use in SwiftUI lists
1414+1515+`BskyService` now covers all current `app.bsky` query/procedure lexicons, including actor/feed/graph/notification plus age-assurance, unspecced, bookmark, and video endpoints.
14161517Built on [CoreATProtocol](https://tangled.org/@sparrowtek.com/CoreATProtocol) for networking, authentication, and token management.
1618···120122|--------|-------------|
121123| `getProfile(for:)` | Fetch a user profile by handle or DID |
122124| `getProfiles(for:)` | Fetch multiple profiles in one request |
125125+| `getSuggestions(limit:cursor:)` | Get actor suggestions |
123126| `getPreferences()` | Get authenticated user's preferences |
124127| `searchActors(query:limit:)` | Search users by name/handle/bio |
125128| `searchActorsTypeahead(query:limit:)` | Fast search for autocomplete |
129129+| `putPreferences(_:)` | Update actor preferences |
126130127131#### Feed Operations
128132129133| Method | Description |
130134|--------|-------------|
131135| `getTimeline(limit:cursor:)` | Get home timeline |
136136+| `getFeed(feed:limit:cursor:)` | Get posts from a feed generator |
132137| `getAuthorFeed(for:limit:cursor:)` | Get a user's posts |
133138| `getPostThread(uri:depth:)` | Get post with replies |
134139| `getPosts(uris:)` | Fetch multiple posts by URI |
140140+| `searchPosts(...)` | Search posts |
141141+| `getQuotes(uri:cid:limit:cursor:)` | Get quotes of a post |
142142+| `getListFeed(list:limit:cursor:)` | Get posts from a list feed |
143143+| `getActorFeeds(for:limit:cursor:)` | Get feed generators by actor |
144144+| `getFeedGenerator(feed:)` | Get one feed generator |
135145| `getFeedGenerators(for:)` | Get custom feed info |
146146+| `getSuggestedFeeds(limit:cursor:)` | Get suggested feed generators |
136147| `getLikes(uri:limit:cursor:)` | Get users who liked a post |
137148| `getRepostedBy(uri:limit:cursor:)` | Get users who reposted |
138149···144155| `getFollowers(for:limit:cursor:)` | Get a user's followers |
145156| `getBlocks(limit:cursor:)` | Get your blocked accounts |
146157| `getMutes(limit:cursor:)` | Get your muted accounts |
158158+| `getRelationships(for:others:)` | Get relationship state for actors |
159159+| `muteActor(_:)` / `unmuteActor(_:)` | Mute or unmute actor |
160160+| `muteThread(root:)` / `unmuteThread(root:)` | Mute or unmute thread |
161161+| `muteActorList(_:)` / `unmuteActorList(_:)` | Mute or unmute actor list |
147162148163#### Notification Operations
149164···152167| `listNotifications(limit:cursor:)` | Get notifications |
153168| `getUnreadCount()` | Get unread notification count |
154169| `updateSeen(at:)` | Mark notifications as read |
170170+| `getNotificationPreferences()` | Read notification preferences |
171171+| `putNotificationPreferences(priority:)` | Update legacy notification preferences |
172172+| `putNotificationPreferencesV2(_:)` | Update v2 notification preferences |
155173156174### RepoService (Write Operations)
157175···178196179197```swift
180198createRecord(repo:collection:record:rkey:) -> CreateRecordResponse
199199+putRecord(repo:collection:rkey:record:validate:swapRecord:swapCommit:) -> PutRecordResponse
200200+applyWrites(repo:writes:validate:swapCommit:) -> ApplyWritesResponse
181201deleteRecord(repo:collection:rkey:)
202202+describeRepo(repo:) -> DescribeRepoResponse
182203getRecord(repo:collection:rkey:) -> GetRecordResponse
183204listRecords(repo:collection:limit:cursor:) -> ListRecordsResponse
205205+listMissingBlobs(limit:cursor:) -> ListMissingBlobsResponse
206206+importRepo(car:)
207207+uploadBlob(data:mimeType:) -> BlobResponse
184208```
185209186210## Rich Text
···215239 print("Link to: \(link.uri)")
216240 case .tag(let tag):
217241 print("Hashtag: #\(tag.tag)")
242242+ case .unknown:
243243+ break
218244 }
219245}
220246```
···314340### Notification Reasons
315341316342```swift
317317-public enum NotificationReason: String {
343343+public enum NotificationReason: Codable, Sendable, Equatable, Hashable {
318344 case like
319345 case repost
320346 case follow
···322348 case reply
323349 case quote
324350 case starterpackJoined
351351+ case unknown(String)
325352}
326353```
327354
···4141repeat {
4242 let timeline = try await service.getTimeline(limit: 100, cursor: cursor)
4343 allPosts.append(contentsOf: timeline.feed)
4444- cursor = timeline.cursor.isEmpty ? nil : timeline.cursor
4444+ cursor = timeline.cursor
45454646 // Limit to 500 posts for this example
4747 if allPosts.count >= 500 { break }
···157157}
158158```
159159160160+Fetch posts from a specific feed generator:
161161+162162+```swift
163163+let feedItems = try await service.getFeed(
164164+ feed: "at://did:plc:xxx/app.bsky.feed.generator/whats-hot",
165165+ limit: 25
166166+)
167167+```
168168+169169+Search posts:
170170+171171+```swift
172172+let results = try await service.searchPosts(query: "swift", limit: 25)
173173+for post in results.posts {
174174+ print(post.record.text ?? "")
175175+}
176176+```
177177+160178## Understanding Timeline Structure
161179162180### Timeline
···164182```swift
165183public struct Timeline: Codable, Sendable {
166184 public var feed: [TimelineItem]
167167- public var cursor: String
185185+ public var cursor: String?
168186}
169187```
170188···176194public struct TimelineItem: Codable, Sendable, Identifiable {
177195 public let post: Post
178196 public let reply: Reply?
197197+ public let reason: TimelineReason?
198198+ public let feedContext: String?
199199+ public let reqId: String?
179200180201 public var id: String {
181181- "\(post.uri ?? "")-\(post.cid ?? "")"
202202+ "\(post.uri)-\(post.cid)"
182203 }
183204}
184205```
···187208188209```swift
189210public struct Post: Codable, Sendable {
190190- public let uri: String?
191191- public let cid: String?
211211+ public let uri: String
212212+ public let cid: String
192213 public let author: Author
193214 public let record: Record
194194- public let replyCount: Int
195195- public let repostCount: Int
196196- public let likeCount: Int
197197- public let indexedAt: String
198198- public let viewer: Viewer
199199- public let labels: [String]
215215+ public let replyCount: Int?
216216+ public let repostCount: Int?
217217+ public let likeCount: Int?
218218+ public let quoteCount: Int?
219219+ public let bookmarkCount: Int?
220220+ public let indexedAt: Date
221221+ public let viewer: FeedViewer?
222222+ public let labels: [AuthorLabels]?
200223 public let embed: Embed?
201224}
202225```
···207230208231```swift
209232public struct Record: Codable, Sendable {
210210- public let text: String
211211- public let type: String
233233+ public let text: String?
234234+ public let type: String?
212235 public let langs: [String]?
213236 public let reply: ReplyDetail?
214214- public let createdAt: String
237237+ public let createdAt: Date?
215238 public let embed: Embed?
216239 public let facets: [Facet]?
217240}
···223246224247```swift
225248if let embed = post.embed {
226226- switch EmbedType(rawValue: embed.type) {
227227- case .image:
249249+ switch embed.type {
250250+ case "app.bsky.embed.images#view":
228251 // Image embed
229252 if let images = embed.images {
230253 for image in images {
···232255 }
233256 }
234257235235- case .external:
258258+ case "app.bsky.embed.external#view":
236259 // Link preview
237260 if let external = embed.external {
238261 print("Link: \(external.title)")
239262 print("URL: \(external.uri ?? "")")
240263 }
241264242242- case .record:
265265+ case "app.bsky.embed.record#view":
243266 // Quote post
244267 if let record = embed.record {
245268 print("Quote: \(record.value?.text ?? "")")
246269 }
247270248248- case .recordWithMedia:
271271+ case "app.bsky.embed.recordWithMedia#view":
249272 // Quote post with images
250273 print("Quote with media")
251274
···91919292> Note: Typeahead search is optimized for speed and returns fewer fields than full search.
93939494+### Suggestions
9595+9696+Use ``BskyService/getSuggestions(limit:cursor:)`` for recommendation-style actor suggestions:
9797+9898+```swift
9999+let suggestions = try await service.getSuggestions(limit: 20)
100100+for actor in suggestions.actors {
101101+ print("@\(actor.handle)")
102102+}
103103+```
104104+94105## Understanding Viewer State
9510696107The ``Viewer`` struct indicates the relationship between the authenticated user and a profile:
···127138for savedFeed in preferences.saved {
128139 print("Saved: \(savedFeed)")
129140}
141141+142142+// Update preferences payload when needed
143143+try await service.putPreferences([
144144+ "adultContentEnabled": false
145145+])
130146```
131147132148## Profile Model Reference
+3-1
Sources/bskyKit/Documentation.docc/bskyKit.md
···4455## Overview
6677-bskyKit provides a type-safe, Swift-native interface to the Bluesky AT Protocol APIs. Built on top of CoreATProtocol, it offers comprehensive support for reading and writing social data including profiles, timelines, posts, follows, and notifications.
77+bskyKit provides a Swift-native interface to Bluesky AT Protocol APIs. Built on top of CoreATProtocol, it supports the full `app.bsky` query/procedure surface, including stable typed models and `JSONValue` for endpoints with rapidly evolving schemas.
8899### Key Features
1010···1414- **Rich Text**: Auto-detect mentions, links, and hashtags with proper byte indexing
1515- **Write Operations**: Create posts, likes, reposts, follows, and blocks
1616- **Notifications**: List notifications and manage read state
1717+- **Complete app.bsky Coverage**: Actor, feed, graph, notification, bookmark, age-assurance, unspecced, and video endpoints
17181819### Quick Start
1920···110111111112- ``Feed``
112113- ``Feeds``
114114+- ``JSONValue``
113115- ``Creator``
114116- ``Preferences``
115117- ``Blocks``