You are viewing v3.0.0 of the docs. The latest is v3.1.2.
Docs/Mobile SDK/API Reference

OctetVerdict

The outcome of a predicate call. Returned by isWithin, isOutside, contains on OctetSdk.loc.

Shape

public struct OctetVerdict: Sendable {
    public let result: Result
    public let reason: ReasonCode
    public let message: String
    public let proof: LocationProof?
    public let validity: Interval?
    public let queriedAt: Date
    public let confidence: ConfidenceSummary
    public let achievableLevel: ProofLevel?   // non-nil only when reason == .insufficientPrecision

    public enum Result: Sendable, Hashable {
        case yes, no, indeterminate
    }

    public enum ReasonCode: Sendable, Hashable {
        case ok
        case noFix
        case futureTime
        case staleFix
        case noProofAtResolution
        case insufficientPrecision
        case attestationFailed
        case mockLocationDetected
        case sdkNotRunning
        case notYetReleased
        case invalidSessionNonce
        case invalidDecisionRef
        case invalidDecisionTime
        case spoofingDetected
        case tampering
        case regionUnresolved
    }
}
data class OctetVerdict(
    val result: Result,
    val reason: ReasonCode,
    val message: String,
    val proof: LocationProof?,
    val validity: Interval?,
    val queriedAt: Instant,
    val confidence: ConfidenceSummary,
    val achievableLevel: ProofLevel? = null,  // non-null only when reason == INSUFFICIENT_PRECISION
) {
    enum class Result { YES, NO, INDETERMINATE }

    enum class ReasonCode {
        OK, NO_FIX, FUTURE_TIME, STALE_FIX, NO_PROOF_AT_RESOLUTION,
        INSUFFICIENT_PRECISION, ATTESTATION_FAILED, MOCK_LOCATION_DETECTED,
        SDK_NOT_RUNNING, NOT_YET_RELEASED, INVALID_SESSION_NONCE,
        INVALID_DECISION_REF, INVALID_DECISION_TIME, SPOOFING_DETECTED, TAMPERING, REGION_UNRESOLVED,
    }
}

Field invariants

  • proof is nil/null iff result == .indeterminate/INDETERMINATE. Enforced as a precondition / require at construction. A YES or NO always carries a proof. An INDETERMINATE never does.
  • validity is non-nil exactly when proof is non-nil. Both move together.
  • queriedAt is the atTime that was passed in. Echoed back so callers do not have to thread it themselves.
  • confidence is always populated, even for INDETERMINATE (with an empty summary in that case).
  • achievableLevel is non-nil only when reason == .insufficientPrecision. It names the best proof level the SDK could reach for this query right now. New in 1.1. The SDK never falls back to this level automatically.

result

Value Meaning
.yes / YES Predicate holds. Proof attached.
.no / NO Predicate provably does not hold. Proof attached.
.indeterminate / INDETERMINATE SDK cannot answer. See reason.

Never collapse .indeterminate to .no silently. See Verdicts for the rationale.

reason

Code Result Meaning
ok / OK YES or NO Proof covered atTime. Predicate evaluated cleanly.
noFix / NO_FIX INDETERMINATE SDK started but no fix yet (also: running on simulator / emulator).
futureTime / FUTURE_TIME INDETERMINATE atTime is in the future beyond ±2 s clock-skew tolerance.
staleFix / STALE_FIX INDETERMINATE atTime falls outside the validity window of any cached proof.
noProofAtResolution / NO_PROOF_AT_RESOLUTION INDETERMINATE Cached proof is too coarse for the query (country proof, city query).
insufficientPrecision / INSUFFICIENT_PRECISION INDETERMINATE Conditions cannot support the requested precision. Read achievableLevel.
attestationFailed / ATTESTATION_FAILED INDETERMINATE Device attestation did not pass.
mockLocationDetected / MOCK_LOCATION_DETECTED INDETERMINATE OS flagged mocked location.
sdkNotRunning / SDK_NOT_RUNNING INDETERMINATE Octet.start(...) was never called or session ended.
notYetReleased / NOT_YET_RELEASED INDETERMINATE Predicate path declared by the API but not available in this SDK build.
invalidSessionNonce / INVALID_SESSION_NONCE INDETERMINATE Supplied sessionNonce is empty or exceeds 512 bytes. No proof is produced. Added in 1.2.
invalidDecisionRef / INVALID_DECISION_REF INDETERMINATE Supplied decisionRef exceeds 256 characters. No proof, no network call. Added in 2.0.
invalidDecisionTime / INVALID_DECISION_TIME INDETERMINATE A query with decisionRef set was pinned to a past atTime. A decision ref asks for a fresh proof, so it can't answer a past moment. No proof, no network call. Leave out decisionRef to ask about a past time. Added in 2.0.
spoofingDetected / SPOOFING_DETECTED INDETERMINATE The spoof pipeline flagged the fix. Adversarial. Added in 2.0.
tampering / TAMPERING INDETERMINATE Device-integrity tampering (root / hook). Also an attached debugger or native tracer outside a sandbox session. Android refuses these since 3.0, as iOS already did. Adversarial. Added in 2.0.
regionUnresolved / REGION_UNRESOLVED INDETERMINATE A country or subdivision predicate could not resolve the region at the required confidence. Since 3.0, also a pinned claimedRegion that is not an assigned ISO 3166 code. A benign measurement gap, not a detected spoof. Added in 2.0.

Adversarial and benign reasons

Added in 2.0, the measurement-related INDETERMINATE reasons split into two classes so a policy layer can treat them differently. The remaining reasons come from the calling code or the SDK build:

  • Adversarial (a detected forgery or compromise): spoofingDetected, mockLocationDetected, tampering, attestationFailed. A policy layer should deny these.
  • Benign (the SDK could not measure): regionUnresolved, insufficientPrecision, noFix, staleFix, futureTime, noProofAtResolution. A policy layer can permit these with a step-up. noProofAtResolution means the proof on hand is too coarse for the question: ask again, or ask at the level the proof covers.
  • Caller or availability (fix the call, not the device): sdkNotRunning, notYetReleased, invalidSessionNonce, invalidDecisionRef, invalidDecisionTime. None of these is adversarial. Fix the calling code rather than stepping up the user.

Both classes carry no proof. The distinction is in the reason, not the result.

message

message is a free-form string for log lines and debug UI. Its wording can change between SDK versions. Use reason for control flow, not message.

proof

LocationProof is the signed protobuf that a verifier checks against Octet's public keys. Its full shape is part of the protocol, not the SDK's public surface. See Serialization for the v1 alpha caveat about how LocationProof is currently rendered through .toJson().

For most integrators the proof is opaque. Forward it to your verifier as-is.

The assurance tier

New in 3.0. LocationProof.spoofingVerdict is the proof's assurance tier: VERIFIED, PLAUSIBLE, and so on. Read it directly, or as spoofing_verdict in the proof JSON, without decoding the proof bytes. Read the tier, not the region granularity, to decide what a YES is worth.

Tier What a YES means
VERIFIED Independent evidence supports the fix.
PLAUSIBLE The fix is consistent with your query but is not independently corroborated.

A disc query (contains(...) or a disc isWithin(...)) answers only from a VERIFIED proof unless you opt in with OctetConfig.advanced.acceptPlausibleContainment. See Predicates. To enforce a tier when verifying, use VerifyOptions.requireVerdict.

if verdict.result == .yes, let proof = verdict.proof, proof.spoofingVerdict == .verified {
    accept(proof)
}
val proof = verdict.proof
if (verdict.result == OctetVerdict.Result.YES && proof?.spoofingVerdict == LocationProofVerdict.VERIFIED) {
    accept(proof)
}

validity

The interval over which the underlying proof answers the predicate as stated. It does not predict how long the verdict will stay true. See Time Semantics.

public struct Interval: Sendable, Hashable {
    public let from: Date
    public let to: Date
}
data class Interval(val from: Instant, val to: Instant)

Inclusive-inclusive, from <= to.

confidence

ConfidenceSummary is always populated and carries a list of flags (mock-location detected, VPN active, GNSS anomaly, and so on). To decide what a YES is worth, read the assurance tier on the proof. A strict caller can accept only a YES whose proof is VERIFIED:

let strictYes = verdict.result == .yes && verdict.proof?.spoofingVerdict == .verified
val strictYes = verdict.result == OctetVerdict.Result.YES &&
                verdict.proof?.spoofingVerdict == LocationProofVerdict.VERIFIED

The SDK never applies this filter. The caller sets the policy.

See also

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