SDKs

iOS

Identify iPhones and iPads with the native Swift SDK. No dependencies, no permission prompts, and jailbreak, simulator and tampering verdicts on the device.

The iOS SDK is written in Swift with no third-party dependencies. It collects device signals within a time budget, submits them with your public key, and returns the server's answer with local verdicts. It never shows a permission prompt.

Requirements

  • iOS 13 or newer (also tvOS 13 and Mac Catalyst 13).
  • Xcode 16 or newer.
  • A public key.

Install

.package(url: "https://github.com/fingerly-io/sdk-ios.git", from: "0.1.0")

In Xcode, add the package from File > Add Package Dependencies and link the Fingerly product to your app target.

Identify a device

SignInViewModel.swift
import Fingerly

let fingerly = try await Fingerly.load(apiKey: "fly_pk_us_production_…")

func signIn(email: String, password: String) async throws {
    let result = try await fingerly.identify(tag: "sign-in")
    try await api.signIn(email: email, password: password, requestId: result.requestId)
}

Load once and keep the client for the life of the app. identify runs on Swift concurrency and never touches the main thread.

Send requestId to your backend with the action, and decide there after reading the stored event with a secret key. See server-side verification.

Configuration

Fingerly.load

  • apiKeyStringrequired
    Your public key. Its prefix decides the regional API.
  • endpointStringDefault ""
    A custom API origin or first-party proxy. Empty uses the region in the key.
  • scheduleScheduleOptionsDefault budgetMs 1200
    The collection budget: budgetMs, defaultSourceTimeoutMs, concurrency and tiers.
  • consentConsentStateDefault .granted
    .granted, .pending or .denied. Until it is .granted, identify and collect read and send nothing and throw ConsentError. Change it with setConsent(_:). See consent.

identify

  • tagString?
    Your own reference for this identification, such as checkout:8412.
  • tiers[SourceTier]Default all
    Which collection tiers to run: .fast, .deferred, or both.
  • submitBoolDefault true
    Set false to collect and compute verdicts without sending anything.
Budget.swift
let fingerly = try await Fingerly.load(
    apiKey: "fly_pk_us_production_…",
    schedule: ScheduleOptions(budgetMs: 800, concurrency: 4)
)

// A screen that cannot wait: only the fast tier.
let result = try await fingerly.identify(tiers: [.fast])

The result

IdentifyResult

  • requestIdString
    Identifies this identification. Send it to your server with the action it protects.
  • visitorIdString
    The stable identifier the server resolved for this device.
  • visitorIsNewBool
    Whether your organization is seeing this visitor for the first time.
  • visitorConfidenceInt
    How sure the identification is, from 0 to 100.
  • identifiableBool
    false when the device gave too little to identify anyone.
  • duplicateBool
    true when the server had already answered this request.
  • stateString
    enriched, or unavailable when the network lookup could not run.
  • suspectScoreInt?
    The server's weighted score. nil when nothing was scored, which is not the same as 0.
  • suspectLevelString?
    low, medium or high.
  • triggers[IdentifyTrigger]
    The signals the server scored: signal, group, weight and confidence.
  • verdictsVerdicts
    Local, advisory verdicts computed on the device.
  • reportSignalReport
    The report that was sent.

Verdicts

VerdictWhat it means
jailbreakThe device is jailbroken.
simulatorThe app is running in the iOS Simulator.
instrumentationAn instrumentation toolkit is attached to the app.
mitmSomething is intercepting the app's encrypted traffic.
automationA debugger or a UI test runner is driving the app.
tamperingThe app's code has been hooked or modified at runtime.
farmThe device looks mass-provisioned or freshly reset.

Each verdict has value, confidence (.low, .medium, .high) and reasons. They are advisory. The server scores the same evidence with your risk weights.

Verdicts.swift
if result.verdicts.jailbreak.value, result.verdicts.jailbreak.confidence == .high {
    // add friction here, and let your server make the final decision
}

Optional: jailbreak app checks

No Info.plist usage description is required. To let the SDK check for well-known jailbreak apps, declare their URL schemes. The SDK only queries the schemes your app declares.

Info.plist
<key>LSApplicationQueriesSchemes</key>
<array>
  <string>cydia</string>
  <string>sileo</string>
  <string>zbra</string>
  <string>filza</string>
</array>

Errors

identify and collect throw ConsentError when consent is not .granted. identify throws a TransportError with message, status and retryable. The SDK already retries network failures, 429 and 5xx up to three attempts with one idempotency key. 401 means the key is wrong; 402 means your organization is not accepting traffic.

Errors.swift
do {
    let result = try await fingerly.identify(tag: "checkout")
} catch let error as TransportError where error.status == 401 {
    // the key is wrong or revoked
} catch {
    // proceed, and let your server treat the missing request ID as missing evidence
}

Objective-C

FingerlyBridge exposes configure(apiKey:endpoint:platform:), identify(tag:submit:completion:) and collect(completion:) to Objective-C. Errors use the domain io.fingerly.sdk with the HTTP status as the code.