Docs/Magistrate/Get Started/Add Magistrate to Your iOS App

Add Magistrate to Your iOS App

The Magistrate module sits on top of the Octet Mobile SDK. One call, decide, fetches your policy, makes the location proofs it needs, and returns the engine's signed decision.

Preview

The Magistrate module is in preview. The API on this page is the one version 0.1.0 will ship. It works with Octet SDK 3.0.0 or later, and sandbox builds need 3.1.0 or later. The packages below become available with the 0.1.0 release, which follows Octet SDK 3.1.0.

Prerequisites

  • iOS 16 or later, Swift 5.9 or later.
  • The Octet Mobile SDK set up in your app, with the App Attest capability and the location and motion usage descriptions in Info.plist. Follow the iOS Quickstart first.
  • A policy published from the Magistrate studio.

1. Add the packages

Add both Swift packages to your app. Swift Package Manager doesn't pass the Octet SDK through Magistrate, so your app adds it as well. Magistrate needs Octet SDK 3.0.0 or later.

dependencies: [
    .package(url: "https://github.com/octetproof/octet-magistrate-ios", exact: "0.1.0"),
    .package(url: "https://github.com/octetproof/octet-sdk-ios", from: "3.0.0"),
],
targets: [
    .target(name: "YourApp", dependencies: [
        .product(name: "Magistrate", package: "octet-magistrate-ios"),
        .product(name: "OctetSDK", package: "octet-sdk-ios"),
    ]),
]

2. Start the SDK and wrap it

import OctetSDK
import Magistrate

let config = OctetConfig(licenseKey: "")   // required by the type, ignored by start
let sdk = try await Octet.start(config: config)
let magistrate = Magistrate(sdk: sdk)

To keep the verified policy across launches, pass a store:

let magistrate = Magistrate(sdk: sdk, persistentStore: KeychainPolicyBundleStore())

3. Ask for a decision

Call decide when the user does something your policy protects, such as opening an account or placing a bet.

guard let license = sdk.licenseStatus else { return }

do {
    let result = try await magistrate.decide(license: license)
    if result.cause != nil {
        // UNKNOWN: the location couldn't be settled, for example no fix yet.
        // Ask the user to retry.
    }
    // Send result.receipt to your backend with the request.
    // Your backend verifies it and decides what happens.
    try await api.submit(order, decisionReceipt: result.receipt)
} catch let error as PolicyError {
    // No decision was made. Treat it as NO.
}

decide runs the whole exchange: it fetches the policy, checks Octet's signature on it, gets a one-time challenge, has the SDK make the proofs, and sends them to the engine. Don't manage the challenge yourself, unless your backend starts the check (see Start the check from your backend).

A deny is a result, not an error. result.isPermit is false and result.reasons says why. When result.cause is set, the answer is UNKNOWN: the proofs couldn't settle the location. A PolicyError means no decision was made at all. See Decision Response to read a result as YES, NO or UNKNOWN.

The app is not where you enforce

result.isPermit is fine for the screen you show next, but a user can change anything that runs on their device. Your backend enforces the decision, from the verified receipt. See Verify and Enforce.

Several policies in one check

decideMany decides up to 16 policies with one set of proofs. Each result carries its policy's name.

let set = try await magistrate.decideMany(license: license,
                                          policies: ["p_…", "p_…"])
for decision in set.results {
    print(decision.policy, decision.decision)
}

Obligations

A policy can attach obligations to a permit, for example a step-up authentication. A permit with obligations counts only once your app and backend have fulfilled every one. Treat an obligation you don't recognise as NO. See Decision Response.

Things to know

  • The first check after start. Location takes a moment to warm up after Octet.start. The first check can come back UNKNOWN, with cause absence. Retry after a few seconds.
  • Real devices for production. Production needs hardware attestation on a real device. A simulator or a debug build signs in with a sandbox token (see the iOS Quickstart), and its results and receipts say env sandbox. Your backend accepts only prod in production. Magistrate checks from a sandbox build need Octet SDK 3.1.0 or later, and a sandbox app registered on your license. Production apps don't.
  • Geofence policies. A policy with a disc needs an Octet SDK release that can prove one. Until then decide throws bindingUnavailable for it. Policies from the Magistrate studio don't use discs.
  • Errors. See Errors for each PolicyError case.

Sample app

The sample app starts the SDK, calls decide and shows the decision, reasons and obligations. It needs a real device.

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