[{"data":1,"prerenderedAt":220},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fsdks\u002Fios":3},{"page":4,"toc":209,"updated":219},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fsdks\u002Fios","iOS","iOS SDK: Identify Devices in Swift","Identify iPhones and iPads with the native Swift SDK. No dependencies, no permission prompts, and jailbreak, simulator and tampering verdicts on the device.",[10,13,18,24,27,39,41,44,49,51,53,56,79,96,101,104,148,149,176,178,183,186,188,194,197,199,204,207],{"type":11,"text":12},"p","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.",{"type":14,"level":15,"text":16,"id":17},"heading",2,"Requirements","requirements",{"type":19,"items":20},"list",[21,22,23],"iOS 13 or newer (also tvOS 13 and Mac Catalyst 13).","Xcode 16 or newer.","A [public key](\u002Fdocs\u002Fapi-keys).",{"type":14,"level":15,"text":25,"id":26},"Install","install",{"type":28,"samples":29},"code",[30,35],{"label":31,"lang":32,"code":33,"check":34},"Swift Package Manager","swift",".package(url: \"https:\u002F\u002Fgithub.com\u002Ffingerly-io\u002Fsdk-ios.git\", from: \"0.1.0\")",false,{"label":36,"lang":37,"code":38},"CocoaPods","ruby","pod 'Fingerly', '~> 0.1'",{"type":11,"text":40},"In Xcode, add the package from **File > Add Package Dependencies** and link the `Fingerly` product to your app target.",{"type":14,"level":15,"text":42,"id":43},"Identify a device","identify-a-device",{"type":28,"samples":45},[46],{"label":47,"lang":32,"code":48},"SignInViewModel.swift","import Fingerly\n\nlet fingerly = try await Fingerly.load(apiKey: \"fly_pk_us_production_…\")\n\nfunc signIn(email: String, password: String) async throws {\n    let result = try await fingerly.identify(tag: \"sign-in\")\n    try await api.signIn(email: email, password: password, requestId: result.requestId)\n}",{"type":11,"text":50},"Load once and keep the client for the life of the app. `identify` runs on Swift concurrency and never touches the main thread.",{"type":11,"text":52},"Send `requestId` to your backend with the action, and decide there after reading the stored event with a secret key. See [server-side verification](\u002Fdocs\u002Fserver-side-verification).",{"type":14,"level":15,"text":54,"id":55},"Configuration","configuration",{"type":57,"title":58,"fields":59},"fields","`Fingerly.load`",[60,65,69,74],{"name":61,"type":62,"text":63,"required":64},"apiKey","String","Your public key. Its prefix decides the regional API.",true,{"name":66,"type":62,"text":67,"default":68},"endpoint","A custom API origin or first-party proxy. Empty uses the region in the key.","\"\"",{"name":70,"type":71,"text":72,"default":73},"schedule","ScheduleOptions","The collection budget: `budgetMs`, `defaultSourceTimeoutMs`, `concurrency` and `tiers`.","budgetMs 1200",{"name":75,"type":76,"text":77,"default":78},"consent","ConsentState","`.granted`, `.pending` or `.denied`. Until it is `.granted`, `identify` and `collect` read and send nothing and throw `ConsentError`. Change it with `setConsent(_:)`. See [consent](\u002Fdocs\u002Fprivacy-and-consent#consent).",".granted",{"type":57,"title":80,"fields":81},"`identify`",[82,86,91],{"name":83,"type":84,"text":85},"tag","String?","Your own reference for this identification, such as `checkout:8412`.",{"name":87,"type":88,"text":89,"default":90},"tiers","[SourceTier]","Which collection tiers to run: `.fast`, `.deferred`, or both.","all",{"name":92,"type":93,"text":94,"default":95},"submit","Bool","Set `false` to collect and compute verdicts without sending anything.","true",{"type":28,"samples":97},[98],{"label":99,"lang":32,"code":100},"Budget.swift","let fingerly = try await Fingerly.load(\n    apiKey: \"fly_pk_us_production_…\",\n    schedule: ScheduleOptions(budgetMs: 800, concurrency: 4)\n)\n\n\u002F\u002F A screen that cannot wait: only the fast tier.\nlet result = try await fingerly.identify(tiers: [.fast])",{"type":14,"level":15,"text":102,"id":103},"The result","the-result",{"type":57,"title":105,"fields":106},"`IdentifyResult`",[107,110,113,116,120,123,126,129,133,136,140,144],{"name":108,"type":62,"text":109},"requestId","Identifies this identification. Send it to your server with the action it protects.",{"name":111,"type":62,"text":112},"visitorId","The stable identifier the server resolved for this device.",{"name":114,"type":93,"text":115},"visitorIsNew","Whether your organization is seeing this visitor for the first time.",{"name":117,"type":118,"text":119},"visitorConfidence","Int","How sure the identification is, from 0 to 100.",{"name":121,"type":93,"text":122},"identifiable","`false` when the device gave too little to identify anyone.",{"name":124,"type":93,"text":125},"duplicate","`true` when the server had already answered this request.",{"name":127,"type":62,"text":128},"state","`enriched`, or `unavailable` when the network lookup could not run.",{"name":130,"type":131,"text":132},"suspectScore","Int?","The server's weighted score. `nil` when nothing was scored, which is not the same as `0`.",{"name":134,"type":84,"text":135},"suspectLevel","`low`, `medium` or `high`.",{"name":137,"type":138,"text":139},"triggers","[IdentifyTrigger]","The signals the server scored: `signal`, `group`, `weight` and `confidence`.",{"name":141,"type":142,"text":143},"verdicts","Verdicts","Local, advisory verdicts computed on the device.",{"name":145,"type":146,"text":147},"report","SignalReport","The report that was sent.",{"type":14,"level":15,"text":142,"id":141},{"type":150,"columns":151,"rows":154},"table",[152,153],"Verdict","What it means",[155,158,161,164,167,170,173],[156,157],"`jailbreak`","The device is jailbroken.",[159,160],"`simulator`","The app is running in the iOS Simulator.",[162,163],"`instrumentation`","An instrumentation toolkit is attached to the app.",[165,166],"`mitm`","Something is intercepting the app's encrypted traffic.",[168,169],"`automation`","A debugger or a UI test runner is driving the app.",[171,172],"`tampering`","The app's code has been hooked or modified at runtime.",[174,175],"`farm`","The device looks mass-provisioned or freshly reset.",{"type":11,"text":177},"Each verdict has `value`, `confidence` (`.low`, `.medium`, `.high`) and `reasons`. They are advisory. The server scores the same evidence with your [risk weights](\u002Fdocs\u002Frisk-weights).",{"type":28,"samples":179},[180],{"label":181,"lang":32,"code":182},"Verdicts.swift","if result.verdicts.jailbreak.value, result.verdicts.jailbreak.confidence == .high {\n    \u002F\u002F add friction here, and let your server make the final decision\n}",{"type":14,"level":15,"text":184,"id":185},"Optional: jailbreak app checks","optional-jailbreak-app-checks",{"type":11,"text":187},"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.",{"type":28,"samples":189},[190],{"label":191,"lang":192,"code":193},"Info.plist","xml","\u003Ckey>LSApplicationQueriesSchemes\u003C\u002Fkey>\n\u003Carray>\n  \u003Cstring>cydia\u003C\u002Fstring>\n  \u003Cstring>sileo\u003C\u002Fstring>\n  \u003Cstring>zbra\u003C\u002Fstring>\n  \u003Cstring>filza\u003C\u002Fstring>\n\u003C\u002Farray>",{"type":14,"level":15,"text":195,"id":196},"Errors","errors",{"type":11,"text":198},"`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.",{"type":28,"samples":200},[201],{"label":202,"lang":32,"code":203},"Errors.swift","do {\n    let result = try await fingerly.identify(tag: \"checkout\")\n} catch let error as TransportError where error.status == 401 {\n    \u002F\u002F the key is wrong or revoked\n} catch {\n    \u002F\u002F proceed, and let your server treat the missing request ID as missing evidence\n}",{"type":14,"level":15,"text":205,"id":206},"Objective-C","objective-c",{"type":11,"text":208},"`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.",[210,211,212,213,214,215,216,217,218],{"id":17,"text":16,"level":15},{"id":26,"text":25,"level":15},{"id":43,"text":42,"level":15},{"id":55,"text":54,"level":15},{"id":103,"text":102,"level":15},{"id":141,"text":142,"level":15},{"id":185,"text":184,"level":15},{"id":196,"text":195,"level":15},{"id":206,"text":205,"level":15},"2026-09-17T16:59:11.000Z",1789667797808]