> ## Documentation Index
> Fetch the complete documentation index at: https://docs.suprsend.com/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS (Headless)

> Integrate the SuprSend Inbox in your iOS app using the native headless SDK to fetch notifications, mark as read, and build your own custom Swift inbox UI.

The SDK provides the **data layer only** — no built-in UI. Bind to its data and
events and render your own.

## Prerequisites

Integration of [iOS SDK](/docs/ios-integration).

## Initialize Feed Client

```swift theme={"system"}
// create feed instance
let feed: Feed = SuprSend.shared.feeds.initialize(options: IFeedOptions?)
```

**Options**

| Parameter  | Type        | Description                                                  |
| ---------- | ----------- | ------------------------------------------------------------ |
| `tenantId` | `String?`   | Tenant to read from. Defaults to "default" tenant            |
| `pageSize` | `UInt?`     | Notifications per page. Defaults to 20                       |
| `stores`   | `[IStore]?` | Filtered views inside feed (see [Stores](/docs/multi-tabs))  |
| `host`     | `FeedHost?` | Override API/socket host via `FeedHost(socketHost:apiHost:)` |

<Warning>
  If you are passing `tenant_id` in feed, make sure to pass the `scope` key while creating [userToken](/docs/client-authentication) passed during identifying user, else a **403** error will be thrown due to scope mismatch.
</Warning>

## Feed Client

### Get Feed Data Store

Returns the current notification store — the list of notifications plus metadata
like page info and badge counts. You can call this anytime to get updated store data.

```swift theme={"system"}
let feedData: IFeedData = feed.data

feedData.notifications     // [IRemoteNotification] — list of notifications
feedData.store             // IStore — active store
feedData.pageInfo          // IPageInfo — .total, .hasMore, .pageSize
feedData.meta              // "badge" — latest notifs count since last opened, per-store unread counts
feedData.apiStatus         // .initial / .loading / .success / .error / .fetchingMore
```

### Initialize Socket for Realtime Updates

```swift theme={"system"}
feed.initializeSocketConnection()
```

<Warning>
  **Keep exactly one active socket per feed.** Multiple live sockets cause
  duplicated events, doubled badge increments, and inflated counts. Avoid:

  * **Recreating the feed without teardown.** `removeAll()` (or
    `removeInstance(_:)`) the old feed *before* calling `feeds.initialize(...)` again.
  * **Owning the feed somewhere short-lived.** A feed re-created with its view
    opens a socket each time; own it in a single, stable place and reuse it.
  * **Skipping cleanup on dismiss/logout.** An unclosed socket keeps running in
    the background — tear down in `deinit` or on logout.
</Warning>

### Fetch Notification Data

Gets the first page of notifications from the SuprSend server and sets it in the
notification store (it also fetches badge counts on the first call).

```swift theme={"system"}
await feed.fetch()
```

### Fetch More Notifications

Gets the next page and appends it to the notification store. Call this only when
`pageInfo.hasMore == true`.

```swift theme={"system"}
await feed.fetchNextPage()
```

### Listening for Updates

Subscribe to `feed.emitter` to keep your UI in sync. It fires two events:

* `.storeUpdate` — the notification store changed (new notification, state
  update, pagination, mutations, socket events). Listen to this and update your local state so that the UI is refreshed.
* `.newNotification` — use this listener to show a toast notification when a new notification is received.

```swift theme={"system"}
public enum InboxEmitterEvents {
    case storeUpdate(IFeedData)                // store changed — re-render UI
    case newNotification(IRemoteNotification)  // new notification arrived
}
```

```swift theme={"system"}
feed.emitter
    .receive(on: RunLoop.main)
    .sink { [weak self] event in
        switch event {
        case .storeUpdate(let data):                self?.render(data)
        case .newNotification(let notification):    self?.showToast(for: notification)
        }
    }
    .store(in: &cancellables)
```

### Removing Feed

Removes the feed client and aborts its socket connection.

```swift theme={"system"}
SuprSend.shared.feeds.removeInstance(feed)  // one instance
SuprSend.shared.feeds.removeAll()           // all active inbox instances
```

```swift theme={"system"}
deinit { SuprSend.shared.feeds.removeAll() }
```

<Note>
  **Reconnecting:** In background mode iOS suspends app activity. When the app comes from background to foreground, remove existing feed instances and reconnect the socket and emitters, then refetch data.
</Note>

### Action Methods

```swift theme={"system"}
// Change active store (when using multi-tab stores)
await feed.changeActiveStore(storeId: String)

// Reset the bell-icon badge count
await feed.resetBadgeCount()

// Mark notifications as seen
await feed.markBulkAsSeen(notificationIds: [String])

// Mark all notifications as read
await feed.markAllAsRead()

// Mark notification as read
await feed.markAsRead(notificationId: String)

// Mark notification as unread
await feed.markAsUnread(notificationId: String)

// Mark notification as interacted
await feed.markAsInteracted(notificationId: String)

// Archive notification
await feed.markAsArchived(notificationId: String)
```

Read more about [seen, read, and interacted](#notification-states).

**Response** returned by the action methods (`markAsRead`, `changeActiveStore`, etc.):

```swift theme={"system"}
public class APIResponse: NSObject, Response {
    public let status: ResponseStatus     // .success or .error
    public let statusCode: StatusCode?    // HTTP status code (Int)
    public let body: ResponseBody?        // [String: String]
    public let error: ResponseError?      // type and message, when status == .error
}
```

## Notification Structure

```swift theme={"system"}
public class IRemoteNotification: NSObject, Codable {
    public let n_id: String                          // unique notif id
    public let message: IRemoteNotificationMessage   // actual notif message content
    public let created_on: TimeInterval              // notif creation timestamp
    public let seen_on: TimeInterval?                // notif seen timestamp
    public let read_on: TimeInterval?                // notif read timestamp
    public let interacted_on: TimeInterval?          // notif interacted timestamp
    public let archived: Bool?                       // if notif is archived
    public let is_pinned: Bool                       // if notif is pinned
    public let expiry: TimeInterval?                 // milliseconds since epoch to expiry
    public let is_expiry_visible: Bool               // show timer to user till expiry
}

public class IRemoteNotificationMessage: NSObject, Codable {
    public let header: String?                        // title
    public let text: String                           // body
    public let subtext: ISubTextObject?               // subtext
    public let avatar: IAvatarObject?                 // avatar img and click url
    public let url: String?                           // action url
    public let actions: [IActionObject]?              // action buttons
    public let extra_data: String?                    // arbitrary JSON string
}
```

## Notification States

* **Seen**: `seen_on` flag is used to check if the notification has been seen in SuprSend analytics. If it's null, the notification has not been seen yet — call `markBulkAsSeen` when the notification enters the viewport and `seen_on` is null.
* **Read**: `read_on` flag is used to check if the notification has been read. This doesn't update SuprSend analytics and is only for visual purposes. If `read_on` is null, show a dot on the notification indicating it's unread. Call `markAsRead` when `read_on` is null, or `markAsUnread` when `read_on` has a timestamp. Marking as read will also set `seen_on` if not already set.
* **Interacted**: `interacted_on` flag is used to set the notification as clicked in SuprSend analytics. If `interacted_on` is null and the user clicks on the notification, call `markAsInteracted`. This will also set `read_on` and `seen_on` if not already set.
