Docs/Mobile SDK/API Reference

Predicates

The three predicate methods on OctetSdk.loc. All three are async (Swift) / suspend (Kotlin) and do not throw. A runtime problem produces an INDETERMINATE verdict.

isWithin

The primary predicate. Tests whether the device's provable location at atTime (default now) is inside region.

public func isWithin(
    region: OctetRegion,
    atTime: Date = Date(),
    sessionNonce: Data? = nil,
    decisionRef: String? = nil,
    forceFresh: Bool = false,
    content: [ContentItem] = []
) async -> OctetVerdict
suspend fun isWithin(
    region: OctetRegion,
    atTime: Instant = Instant.now(),
    sessionNonce: ByteArray? = null,
    decisionRef: String? = null,
    forceFresh: Boolean = false,
    content: List<ContentItem> = emptyList(),
): OctetVerdict

Example

let v = await sdk.loc.isWithin(
    region: .country(isoCode: "US"),
    atTime: Date()
)
if v.result == .yes { ship(v.proof!) }
val v = sdk.loc.isWithin(
    region = OctetRegion.country("US"),
    atTime = Instant.now()
)
if (v.result == OctetVerdict.Result.YES) ship(v.proof!!)

isOutside

Tests whether the device's provable location at atTime (default now) is outside region.

public func isOutside(
    region: OctetRegion,
    atTime: Date = Date(),
    sessionNonce: Data? = nil,
    decisionRef: String? = nil,
    forceFresh: Bool = false,
    content: [ContentItem] = []
) async -> OctetVerdict
suspend fun isOutside(
    region: OctetRegion,
    atTime: Instant = Instant.now(),
    sessionNonce: ByteArray? = null,
    decisionRef: String? = null,
    forceFresh: Boolean = false,
    content: List<ContentItem> = emptyList(),
): OctetVerdict
Caution

isOutside is a separate query, not the negation of isWithin. A YES from isOutside carries a proof that the device is outside region, and a verifier checks that proof the same way as any other. An INDETERMINATE from isWithin does not mean the device is outside. Negating it would merge two outcomes: the device is not inside region, and the SDK could not tell.


contains

Tests whether the point center is within tol meters of the device's provable location.

public func contains(
    center: LatLon,
    tol: Meters,
    atTime: Date = Date(),
    sessionNonce: Data? = nil,
    decisionRef: String? = nil,
    forceFresh: Bool = false,
    content: [ContentItem] = []
) async -> OctetVerdict
suspend fun contains(
    center: LatLon,
    tol: Meters,
    atTime: Instant = Instant.now(),
    sessionNonce: ByteArray? = null,
    decisionRef: String? = null,
    forceFresh: Boolean = false,
    content: List<ContentItem> = emptyList(),
): OctetVerdict

Mathematically equivalent to isWithin(region: .disc(center: center, radiusMeters: tol), atTime:). Kept as a distinct verb because the device-centric reading is the natural one for proximity use cases ("am I near the delivery dropoff", "did I arrive at the geofence").

PLAUSIBLE containment

A disc answer, from contains(...) or a disc isWithin(...), carries an assurance tier on its proof. By default a disc query answers YES or NO only from a VERIFIED proof, and otherwise returns INDETERMINATE. New in 3.0: setting OctetConfig.advanced.acceptPlausibleContainment = true lets it answer from a PLAUSIBLE proof as well. Tight tolerances pass only with a tight location fix.

let config = OctetConfig(
    advanced: AdvancedConfig(acceptPlausibleContainment: true))
val config = OctetConfig(
    advanced = AdvancedConfig(acceptPlausibleContainment = true))

Custom predicates

isWithin is the load-bearing primitive, and contains is a one-line wrapper over it. Custom predicates can be built the same way:

extension OctetLoc {
    func isNear(_ center: LatLon, _ tol: Meters) async -> OctetVerdict {
        await isWithin(region: .disc(center: center, radiusMeters: tol))
    }
    func isInsideAny(_ regions: [OctetRegion]) async -> OctetVerdict? {
        for r in regions {
            let v = await isWithin(region: r)
            if v.result == .yes { return v }
        }
        return nil
    }
}
suspend fun OctetLoc.isNear(center: LatLon, tol: Meters): OctetVerdict =
    isWithin(OctetRegion.disc(center, tol))

suspend fun OctetLoc.isInsideAny(regions: List<OctetRegion>): OctetVerdict? {
    for (r in regions) {
        val v = isWithin(r)
        if (v.result == OctetVerdict.Result.YES) return v
    }
    return null
}

Time semantics

All three predicates take an optional atTime. See Time Semantics for the live / historical / future regimes and the per-resolution validity windows. Default is "now". Future times beyond ±2 s come back INDETERMINATE / FUTURE_TIME.

Proof binding and freshness

All three predicates take three optional controls over which proof answers the call:

  • sessionNonce (opaque bytes, 1 to 512) binds the proof to one login. The SDK commits a hash of the nonce inside the signed proof, so a verifier can confirm the proof was made for that login. An empty or over-512-byte nonce returns INDETERMINATE / INVALID_SESSION_NONCE.
  • decisionRef (1 to 256 characters) binds the uploaded proof to one authorization decision, tagging the upload envelope with decision_ref and region_ref. An over-256-character ref returns INDETERMINATE / INVALID_DECISION_REF, with no network call. Pass nil / null, not an empty string, when there is no decision.
  • forceFresh bypasses the freshness buffer and mints a proof for this call, with none of the binding semantics above.

Each of sessionNonce, decisionRef, and content forces a fresh proof. Setting forceFresh as well has no additional effect.

let nonce = try await loginBackend.issueNonce()
let v = await sdk.loc.isWithin(region: .country(isoCode: "US"), sessionNonce: nonce)
if v.result == .yes { send(v.proof!, to: loginBackend) }

Proof Binding explains when to use each, and how a bound proof is verified.

Content

New in 3.1. All three predicates take an optional content list. The device signs a commitment to each item into the proof: its key, content type, size, and SHA256(salt ‖ value). Unless you mark an item for upload, the content itself stays on the device. An empty list (the default) adds nothing.

public struct ContentItem: Sendable, Equatable {
    public let key: String
    public let contentType: String
    public let locator: String?                // where you will keep the content; not signed
    public var salt: Data { get }              // the salt the item is committed with
    public static func bytes(key: String, contentType: String, _ data: Data, salt: Data? = nil,
                             upload: Bool = false, locator: String? = nil) -> ContentItem
    public static func file(key: String, contentType: String, _ url: URL, salt: Data? = nil,
                            upload: Bool = false, locator: String? = nil) -> ContentItem
    public static func digest(key: String, contentType: String, _ digest: ContentDigest,
                              uploadFrom: URL? = nil, uploadSalt: Data? = nil,
                              locator: String? = nil) -> ContentItem
    public static func generateSalt() -> Data
}
public struct ContentDigest: Sendable, Equatable {
    public let entryDigest: Data
    public let size: UInt64
    public let salt: Data
}
public struct ContentError: Error, Sendable, Equatable { public let message: String }

extension Octet {
    public static func hashContent(file: URL, salt: Data? = nil) async throws -> ContentDigest
}
class ContentItem {
    val key: String
    val contentType: String
    val locator: String?                       // where you will keep the content; not signed
    val salt: ByteArray                        // the salt the item is committed with
    companion object {
        fun bytes(key: String, contentType: String, data: ByteArray, salt: ByteArray? = null,
                  upload: Boolean = false, locator: String? = null): ContentItem
        fun file(key: String, contentType: String, file: File, salt: ByteArray? = null,
                 upload: Boolean = false, locator: String? = null): ContentItem
        fun digest(key: String, contentType: String, digest: ContentDigest,
                   uploadFrom: File? = null, uploadSalt: ByteArray? = null,
                   locator: String? = null): ContentItem
        fun generateSalt(): ByteArray
    }
}
class ContentDigest(entryDigest: ByteArray, val size: Long, val salt: ByteArray)
class ContentError(message: String) : Exception(message)

// on Octet:
suspend fun hashContent(file: File, salt: ByteArray? = null): ContentDigest
  • bytes hashes a value in memory during the call.
  • file streams a file during the call, up to 64 MiB.
  • digest takes a digest from Octet.hashContent, which hashes a file of any size before the call. hashContent throws ContentError for a bad salt or an unreadable file.
  • salt defaults to a random 32-byte salt that the SDK generates. Read it back from ContentItem.salt (or ContentDigest.salt) and keep it with the content. Pass an empty value to commit unsalted, or your own salt of 16 or 32 bytes. generateSalt() returns 32 random bytes.
  • upload (or uploadFrom on a digest item) has the SDK upload the content to your own storage after the proof is made. It needs caller content uploads enabled for your license. See Uploading content.
  • locator is an identifier you choose for where the content will be kept, up to 256 bytes. It travels with the proof unsigned and is passed to your upload provider. Never put a credential in it.

Content that breaks a rule (1 to 16 items, unique keys of 1 to 64 bytes, a type/subtype content type with no parameters, a valid salt, a readable file of at most 64 MiB) returns INDETERMINATE / INVALID_CALLER_CONTENT with no proof. If caller content isn't enabled for your license, a query with content returns INDETERMINATE / CALLER_CONTENT_NOT_ENABLED with no proof. Without location authorization, a query with content returns INDETERMINATE / PERMISSION_REQUIRED.

Content Commitments explains what a commitment proves and how a verifier checks it.

See also

TermsPrivacy© 2026 Understone, Inc. d/b/a Octet