# Add Magistrate to Your Android 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.

:::note[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

- Android `minSdk` 30 or later, the Octet Mobile SDK's minimum.
- The Octet Mobile SDK set up in your app, with the `ACCESS_FINE_LOCATION` permission. Follow the [Android Quickstart](/docs/mobile/getting-started/android-quickstart/) first.
- A release-signed build on a real device for production. A debug build or an emulator needs a sandbox token.
- A policy published from the [Magistrate studio](/docs/magistrate/get-started/write-a-policy/).

## 1. Add the dependency

Add the Magistrate Maven repository and the dependency. Magistrate brings in the Octet SDK (`com.octetproof:sdk`, 3.0.0 or later) for you.

```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        maven("https://raw.githubusercontent.com/octetproof/octet-magistrate-android/mvn-repo")
    }
}

// app/build.gradle.kts
dependencies {
    implementation("com.octetproof:magistrate:0.1.0")
}
```

## 2. Start the SDK and wrap it

```kotlin
import com.octetproof.magistrate.Magistrate
import com.octetproof.sdk.api.Octet
import com.octetproof.sdk.api.OctetConfig

val config = OctetConfig(licenseKey = "")   // required by the type, ignored by start
val sdk = Octet.start(context = applicationContext, config = config)
val magistrate = Magistrate(sdk)
```

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

```kotlin
val magistrate = Magistrate(sdk, persistentStore = EncryptedPrefsPolicyBundleStore(context))
```

## 3. Ask for a decision

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

```kotlin
val license = sdk.licenseStatus ?: return

try {
    val result = magistrate.decide(license)
    if (result.cause != null) {
        // 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.
    api.submit(order, decisionReceipt = result.receipt)
} catch (e: PolicyException) {
    // 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](/docs/magistrate/get-started/verify-and-enforce/#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 `PolicyException` means no decision was made at all. See [Decision Response](/docs/magistrate/reference/decision/#yes-no-and-unknown) to read a result as YES, NO or UNKNOWN.

:::warning[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](/docs/magistrate/get-started/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.

```kotlin
val set = magistrate.decideMany(license, policies = listOf("p_...", "p_..."))
set.results.forEach { println("${it.policy} ${it.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](/docs/magistrate/reference/decision/#obligations).

## 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 with a release-signed build. An emulator or a debug build signs in with a sandbox token (see the [Android Quickstart](/docs/mobile/getting-started/android-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](/docs/magistrate/reference/errors/) for each `PolicyException` case.

## Sample app

The [sample app](https://github.com/octetproof/octet-magistrate-android/tree/main/sample) starts the SDK, calls `decide` and shows the decision, reasons and obligations. It needs a real device and a release-signed build.
