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

# iOS Reference

> The full API signatures, models and error definitions of the OneS1ght iOS SDK.

<Info>
  This document targets **v0.1.13**. You can check the running version with `OneS1ght.sdkVersion`.
</Info>

`OneS1ght` is the single static entry point for the whole app — you never create an instance, and every API is
called on the type. All APIs are called on the **main actor (@MainActor)**.

```swift theme={null}
import OneS1ght
```

## Lifecycle

| API                                                  | Description                                                                                                                                                                                                           |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `initialize(sdkKey:geoSdkKey:baseURL:) async throws` | Once at app start. Device gate → key validation → config load. Idempotent — a repeat call after success is ignored, a repeat call after failure retries, and **calling it with a different key rebuilds the session** |
| `permissions() async -> PermissionStatus`            | Checks positioning permission — **calling it shows the system prompt**                                                                                                                                                |
| `reset() async`                                      | Discards the session. You can then `initialize` with a different key (for runtime key swaps)                                                                                                                          |
| `setLanguage(_ code: String?)`                       | Language for SDK logs and messages — `"ko"`·`"ja"`·`"en"`. `nil` follows the device language (default). `0.1.11~`                                                                                                     |

Parameters of `initialize`:

| Parameter   | Type                               | Description                                                                                                                                                             |
| ----------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sdkKey`    | `String`                           | Issued in the OneS1ght console (`ock_sdk_…`) — auth, zones, collection, events, floor plans                                                                             |
| `geoSdkKey` | `String?` (default `nil`)          | **Temporary parameter** — a transitional value for looking up locators, sessions and floors, handed over by your contact. **Omit it and positioning alone is disabled** |
| `baseURL`   | `URL` (default: production server) | Only for customers running their own server. Development and production are separated by the **key**, not by this parameter                                             |

The default `baseURL` is `https://console.ones1ght.com/api/sdk/v1`.

<Warning>
  **`initialize` does not look up buildings or floors.** Setting the space is the job of `setFloorMap` —
  skip it and the positioning pipeline runs but produces no coordinates (`E3001`).
</Warning>

## Positioning session — FloorSession

<Tabs>
  <Tab title="SwiftUI">
    ```swift theme={null}
    .task {
        guard let session = try? OneS1ght.floorSession() else { return }
    }
    ```
  </Tab>

  <Tab title="UIKit">
    ```swift theme={null}
    override func viewDidLoad() {
        super.viewDidLoad()
        guard let session = try? OneS1ght.floorSession() else { return }
    }
    ```
  </Tab>
</Tabs>

| API                                     | Description                                                                                                                     |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `floorSession() throws -> FloorSession` | Gets the session. **Always the same instance** — there is only one UWB radio, judgement engine and coordinate buffer per device |
| `session.begin() async throws`          | Starts positioning (when the user enters the store)                                                                             |
| `session.begin(provider:) async throws` | Injects a custom positioning source — for special paths such as simulator testing (`MockPositioningProvider`)                   |
| `session.end() async`                   | Stops positioning and uploads the remaining coordinates. Initialization state is kept → call `begin` again to restart           |
| `session.floor: Floor?`                 | The floor currently set                                                                                                         |
| `session.isRunning: Bool`               | Whether positioning is running                                                                                                  |

## Callbacks

Session callbacks live on the `FloorSession` instance; the debug log lives on the `OneS1ght` type.

| Callback              | Type                              | Description                                                                                           |
| --------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `session.onPosition`  | `((Coordinates) -> Void)?`        | Live coordinates (floor-plan local meters) — up to 4 times per second                                 |
| `session.onZoneEnter` | `((Zone) -> Void)?`               | Zone enter — fires as soon as the on-device judgement is made (no server round trip)                  |
| `session.onZoneExit`  | `((Zone) -> Void)?`               | Zone exit                                                                                             |
| `session.onZoneDwell` | `((Zone, TimeInterval) -> Void)?` | Dwell tick — (zone, seconds spent)                                                                    |
| `session.onTriggers`  | `((String, [Trigger]) -> Void)?`  | (zoneId, triggers) — personalized actions **matched by the server**                                   |
| `OneS1ght.onDebugLog` | `((LogLevel, String) -> Void)?`   | Internal SDK activity log. Register it before `initialize` so you do not miss the initialization logs |

<Warning>
  As of `0.1.12`, `onDebugLog` gives you **the level along with the text** (it was
  `(String) -> Void`). See the [migration guide](/sdk/integration/ios/migration-guide).
</Warning>

### LogLevel

```swift theme={null}
public enum LogLevel { case log, info, warn, error }   // log < info < warn < error
```

It's `Comparable`, so you can filter with `level >= .warn`.

| Level    | Meaning                                                      | Example                                                                    |
| -------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- |
| `.log`   | Flow record — you don't normally need to read it             | Coordinates sent, zones injected                                           |
| `.info`  | Worth knowing — normal, but useful to notice                 | No zones on this floor, live stream connected                              |
| `.warn`  | Needs a look — not broken, but it won't behave as you expect | Aggregation interval shorter than the coordinate period, so IN never fires |
| `.error` | Broken — that feature won't work until you fix it            | Not enough anchors to position, session ID not set                         |

<Note>
  "No zones registered on this floor" is a **normal state** — nobody has drawn a zone in
  the console yet — so it is `.info`, not a failure.
</Note>

<Warning>
  Only `onTriggers` comes from the server — if the network drops, the enter judgement still arrives but the triggers do not.
</Warning>

## Space lookup

One method per endpoint, with a list and a single-item pair.

| API                                            | Description                                                                                  |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `buildings() async throws -> [Building]`       | Building list. Empty when initialized without `geoSdkKey`                                    |
| `building(_:) async throws -> Building`        | A single building                                                                            |
| `floors(_:) async throws -> [Floor]`           | Floor list — **no floor plan image** (`image == nil`, keeps the list light)                  |
| `floor(_:_:) async throws -> Floor`            | A single floor — includes the floor plan image (served from cache, so it adds no round trip) |
| `zones(_:_:) async throws -> [Zone]`           | Zones on a floor                                                                             |
| `zone(_:_:_:) async throws -> Zone`            | A single zone                                                                                |
| `locators(_:_:) async throws -> FloorLocators` | Locator layout and UWB session ID for a floor                                                |

## Setting the floor

| API                                       | Description                                                                                                                                                            |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setFloorMap(_:buildingID:) async throws` | Sets the floor — injects locators, session and zones into the internal engine. While running it **switches floors immediately** (the session is kept). `nil` clears it |
| `refreshZones() async -> [Zone]`          | Re-fetches only the zones of the current floor (light — no floor plan re-download). Applied to the judgement engine immediately                                        |

## Profiles

| API                                               | Description                                                                                  |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `createProfile(_:) async throws -> String`        | Creates a profile — returns the server-issued `profileId`. **Your app stores and reuses it** |
| `getProfile(_:) async throws -> [String: String]` | Reads the attributes                                                                         |
| `putProfile(_:_:) async throws`                   | **Replaces** the attributes entirely (not a partial update)                                  |
| `deleteProfile(_:) async throws`                  | Deletes the profile (collected coordinates and events follow the retention policy)           |
| `identify(profileId:)`                            | Connects it — required before positioning. Pass `nil` on sign-out                            |

<Note>
  Your member IDs never reach the server — the mapping stays with you.
</Note>

<Warning>
  `savedProfileId ?? (try await OneS1ght.createProfile(…))` **does not compile.**
  The right-hand side of `??` is an autoclosure and cannot carry `try await` — use `if let` instead.
</Warning>

## Data upload

Coordinates are uploaded at **300 points or 60 seconds**, whichever comes first.
The remainder is also sent when the app goes to the background and on `end()`.

| API            | Description                                |
| -------------- | ------------------------------------------ |
| `send() async` | Uploads the buffer right now               |
| `empty()`      | **Drops** the buffer — nothing is uploaded |

## State

| API                  | Type                 | Description                                                                             |
| -------------------- | -------------------- | --------------------------------------------------------------------------------------- |
| `isInitialized`      | `Bool`               | Whether initialization succeeded (device passed + key valid + config loaded)            |
| `deviceAvailability` | `DeviceAvailability` | Whether positioning is possible, and why not. Callable before `initialize` (no network) |
| `isDeviceAvailable`  | `Bool`               | Short form of the above (`== .available`)                                               |
| `sdkVersion`         | `String`             | SDK version (for example `"0.1.13"`)                                                    |

```swift theme={null}
public enum DeviceAvailability: Equatable {
    case available            // positioning is possible
    case osVersionTooLow      // below iOS 27 — tell the user to update the OS
    case deviceNotSupported   // no UWB chip — tell the user an iPhone 12 or later is required
}

public enum PermissionStatus: Equatable {
    case authorized           // positioning can start
    case denied               // the app cannot ask again — send the user to Settings
    case unsupported          // positioning is not possible on this device or OS
}
```

## Errors — SdkError

`SdkError` covers the five errors the SDK itself throws. Communication failures arrive as `ApiError`.
Both types expose an [error code](/en/sdk/faq/error-code) through `.code`.

<Tabs>
  <Tab title="SwiftUI">
    ```swift theme={null}
    .task {
        do {
            try await OneS1ght.initialize(sdkKey: key)
        } catch let e as SdkError {
            print(e.code.rawValue)                  // "E1003"
        } catch let e as ApiError {
            print(e.code.rawValue, e.description)   // "E5005" · "failed to decode the response — …"
        }
    }
    ```
  </Tab>

  <Tab title="UIKit">
    ```swift theme={null}
    Task { @MainActor in
        do {
            try await OneS1ght.initialize(sdkKey: key)
        } catch let e as SdkError {
            print(e.code.rawValue)                  // "E1003"
        } catch let e as ApiError {
            print(e.code.rawValue, e.description)   // "E5005" · "failed to decode the response — …"
        }
    }
    ```
  </Tab>
</Tabs>

| Case                           | Code    | When it happens                                  | What to do                                      |
| ------------------------------ | ------- | ------------------------------------------------ | ----------------------------------------------- |
| `SdkError.notInitialized`      | `E1001` | Another API called without `initialize`          | Fix the call order                              |
| `SdkError.notIdentified`       | `E1004` | Positioning started without `identify`           | Call `identify(profileId:)` right after sign-in |
| `SdkError.positioningDisabled` | `E1003` | The key is valid but positioning is switched off | Check the console settings · contact support    |
| `SdkError.osVersionTooLow`     | `E2001` | Below iOS 27                                     | Tell the user to update the OS                  |
| `SdkError.deviceNotSupported`  | `E2002` | No UWB chip                                      | Branch ahead of time with `deviceAvailability`  |
| `ApiError.invalidKey`          | `E1002` | The key is wrong or revoked (401)                | Check the key in the console · reissue it       |
| `ApiError.forbidden`           | `E5004` | Another tenant's resource (403)                  | Check the key scope                             |
| `ApiError.notFound`            | `E5003` | Target not found (404)                           | Usually a contract mismatch                     |
| `ApiError.unprocessable`       | `E5003` | Payload problem (422)                            | Suspect an SDK / server version mismatch        |
| `ApiError.server`              | `E5002` | Server 5xx                                       | Ask your integration administrator              |
| `ApiError.network`             | `E5001` | Offline or timeout                               | Retried automatically                           |
| `ApiError.decoding`            | `E5005` | Response JSON does not match                     | The reason is in `description`                  |

Positioning runtime errors (`E4001`–`E4003`) and space setup errors (`E3001`–`E3004`) are not thrown — they are **logged only**.
Check them through `onDebugLog` or the console log analyzer. [Full list](/en/sdk/faq/error-code)

## Models

### Coordinates — live coordinates

```swift theme={null}
public struct Coordinates: Codable, Equatable {
    public let x: Double   // meters
    public let y: Double   // meters
    public let z: Double   // height (0 for 2D positioning)
}
```

### Building · Floor

| Type       | Field                             | Description                                                                                        |
| ---------- | --------------------------------- | -------------------------------------------------------------------------------------------------- |
| `Building` | `id` · `name`                     | Building ID · name                                                                                 |
|            | `floorCount: Int?`                | Number of floors (`nil` when the server does not provide it)                                       |
| `Floor`    | `id` · `name`                     | Floor ID · name                                                                                    |
|            | `image: Data?`                    | Floor plan PNG — `nil` when it comes from `floors(_:)`, filled in when it comes from `floor(_:_:)` |
|            | `hasPlan: Bool`                   | Whether a floor plan is registered                                                                 |
|            | `originX` · `originY`             | Floor plan origin offset (meters)                                                                  |
|            | `widthM` · `heightM`              | Real size of the floor plan (meters)                                                               |
|            | `minX` · `minY` · `maxX` · `maxY` | Placement bounds (derived from origin + size)                                                      |

### Locator · FloorLocators

| Type            | Field                    | Description                                                             |
| --------------- | ------------------------ | ----------------------------------------------------------------------- |
| `Locator`       | `address: Int`           | Last 2 bytes of the UWB MAC (for example `0x9DD7`)                      |
|                 | `x` · `y` · `z`          | Floor-plan local meters                                                 |
| `FloorLocators` | `locators: [Locator]`    | Locators installed on this floor                                        |
|                 | `sessionId: Int?`        | UWB session — different per floor. `nil` means positioning cannot start |
|                 | `positioningReady: Bool` | Whether there are locators and a session (derived)                      |

### Zone

| Field                         | Type         | Description                                                                 |
| ----------------------------- | ------------ | --------------------------------------------------------------------------- |
| `id` / `name`                 | `String`     | Zone identifier · name                                                      |
| `polygon`                     | `[Position]` | Vertices in order — `Position` is floor-plan local meters (x, y)            |
| `inDist`                      | `Double`     | Enter distance threshold (m) — default 3.0                                  |
| `inCount` / `inCountInterval` | `Int`        | Detections required to confirm an enter · interval between counts (seconds) |
| `outPeriod`                   | `Int`        | Grace period before an exit is decided                                      |
| `priority`                    | `Int`        | Priority when zones overlap                                                 |
| `callInout`                   | `Bool`       | Whether enter/exit callbacks are emitted                                    |
| `dwellSeconds`                | `Int?`       | Dwell tick interval (seconds) — `nil` means the default of 5                |

These judgement parameters come from the zone metadata on the server and are set per zone in the console under
**Space management → select a zone → SDK zone judgement** ([how to set them](/en/locator/areas)).
You can also test whether an arbitrary point is inside a zone with `zone.contains(Position(x:y:))`.

<Warning>
  When `inCount` is `0`, an enter is never confirmed and no zone event fires.
  New zones created in the console default to `1`; if you have older zones set to `0`, check them in the console.
</Warning>

### Trigger — personalized action

| Field        | Type                | Description                                     |
| ------------ | ------------------- | ----------------------------------------------- |
| `trigger_id` | `String`            | Trigger identifier                              |
| `type`       | `String`            | `signage · coupon · tracking · merch · generic` |
| `payload`    | `[String: String]?` | Type-specific extra data                        |

### MockPositioningProvider — for testing

```swift theme={null}
public final class MockPositioningProvider: PositioningProvider {
    public init()
    public func simulateEnter(buildingId: String)
    public func simulatePosition(_ c: Coordinates, floorId: String, at: Date = Date())
    public func simulateZone(_ zoneId: String, status: ZoneEventStatus, floorId: String, at: Date = Date())
}
```

Inject it with `session.begin(provider:)` to exercise the SDK pipeline without UWB.
`ZoneEventStatus` is `.enter` · `.dwell` · `.exit`.

## Threading

* Every `OneS1ght` API is `@MainActor`. Call them directly from SwiftUI views and `.task`; from a background
  context, wrap them in `await MainActor.run { … }`.
* Callbacks (`onPosition` and the rest) are also invoked on the main actor — updating the UI directly is safe.

## Known limitations

<Warning>
  UWB on iOS is **foreground only**. When the app goes to the background, positioning stops and the buffer is flushed;
  it resumes when the app comes back. This is a platform restriction the SDK cannot work around.
</Warning>

<Warning>
  The coordinate buffer lives in memory. If the app is force-quit, coordinates that have not been uploaded are lost (`E5006`).
</Warning>

<Warning>
  There is no LICENSE file yet. Terms of use follow your separate agreement.
</Warning>
