> ## 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 SuprSend inbox in iOS using the native SDK. Data layer only — bring your own 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.
