Skip to content

API Reference

import DeaSDK
import DeaBase // for Consent, SessionInfo, JSONValue and the other shared types

There is no shared instance: create one DeaSdk and keep it. See the Quick Start.

DeaSdk

init(options:)

init(options: DeaSdkOptions)

Creates the SDK and starts a session. Capture does not begin until start(). Never throws: if the scoring engine cannot load, the instance is still created, ready is false and coreFault says why. With model updates on, it also starts a check for a newer model in the background. See Options.

start

func start()

Starts global capture. Call it once, on the main thread, as early as possible. Does nothing after dispose().

options

let options: DeaSdkOptions

The options this instance was created with.

appKey

var appKey: String? { get }

The app key the SDK sends as X-App-Key: the appKey option, trimmed, or nil when none was given.

ready

var ready: Bool { get }

Whether the SDK is able to score. When false, capture continues but there is no score, the classification is .insufficient, and coreFault says why.

coreFault

private(set) var coreFault: String?

Why the SDK cannot score, or nil.

getClassification

func getClassification() throws -> Classification

The one score read: waits for input already received to be scored, then reads the score, the amount of evidence behind it and the loaded model's thresholds in one snapshot, and classifies it as .human, .bot, .undetermined or .insufficient. Does not call the network. Blocks for as long as pending input takes to be scored — usually nothing — so call it before a decision, not on every frame.

Before the scoring engine loads (ready is false) it returns Classification.unloaded (.insufficient, value nil) and does not throw.

Throws: ThresholdMissing when the loaded model carries no bot threshold. See Human Scoring.

drain

func drain()

Waits until input the app has already received has been scored. getClassification() does this itself.

modelInfo

var modelInfo: ModelInfo { get }

Which model is scoring now: its version (0 for the model built into the SDK) and where it came from. See Options.

checkForModel

func checkForModel(force: Bool = false, completion: ((ModelCheck) -> Void)? = nil)

Asks DEA's model server for a newer model now, if a day has passed since the last check that got an answer; otherwise answers .notDue without a request. The SDK already checks when it is created and when the app returns to the foreground, so you do not need to call this.

Parameter Type Description
force Bool Ask even if the last check was less than a day ago. For a person pressing a button, never for a schedule
completion ((ModelCheck) -> Void)? Called with how the check ended, off the main thread

Never throws and never blocks. With modelUpdates: false, completion gets .cancelled.

modelChanges

@discardableResult
func modelChanges(_ listener: @escaping (ModelInfo) -> Void) -> () -> Void

Calls listener, off the main thread, each time a newer model starts scoring during this run. Call the returned closure to stop listening.

var consent: Consent { get }

The user's current consent answer. See User Consent.

setConsent

func setConsent(_ granted: Bool, remember: Bool = true)
Parameter Type Description
granted Bool Whether the user agreed
remember Bool Store the answer on the device. false keeps it for this run only

Takes effect immediately: granting starts recording, withdrawing stops it.

requestConsent

func requestConsent(
    from presenter: UIViewController,
    copy: ConsentCopy = ConsentCopy(),
    remember: Bool = true,
    completion: @escaping (Bool) -> Void
)

UIKit. Shows the consent alert from presenter if the user has not answered, records the answer, and calls completion with it. With a stored answer and remember: true, calls completion at once and shows nothing.

pause

func pause()

Stops recording. The session is kept. See Sensitive Screens.

resume

func resume()

Starts recording again after pause(). Does not override consent.

setMotion

func setMotion(_ on: Bool)
var motion: Bool { get }

Turns the motion sensors off, or back on. Core Motion keeps running from init until dispose(), including while capture is paused; setMotion(false) stops it. While motion is off, the scoring engine receives no motion readings and scores touches with zeros in their motion values, which can change the classification. motion reads whether the sensors are on.

captureEvent

func captureEvent(_ name: String, data: [String: JSONValue]? = nil) -> Bool
Parameter Type Description
name String 1 to 64 characters
data [String: JSONValue]? Optional; at most 1024 bytes as JSON

Returns: true when recorded; false when a limit is exceeded, capture is paused, consent is missing, or the SDK is disposed. See Custom Events.

captureKey

func captureKey(
    _ keyClass: KeyClass,
    pressed: Bool = true,
    mods: Int = 0,
    repeat: Bool = false,
    field: String? = nil
)
Parameter Type Description
keyClass KeyClass The kind of key, never the character
pressed Bool false reports the release; needs .keyUp in events
mods Int Modifier keys held, as KeyMod bits. A soft keyboard reports none: 0
repeat Bool Whether the press is an automatic repeat
field String? A name for the text field typed into

Reports one keystroke. Does nothing unless .keyDown is in the events option, and, like every capture, while paused or before consent. Never call it for a password field. See Report typing.

reset

func reset()

Ends the current session and starts a new one. A model the SDK downloaded stays in use. See Sessions.

sessionInfo

var sessionInfo: SessionInfo { get }

The current session id, device and SDK version.

coreCounts

var coreCounts: CoreCounts { get }

Diagnostics: how many captured events the SDK has processed in this session. samples grows as the user interacts; use it to check that capture works.

attest

func attest() -> Attestation

Registers the device key with DEA. Cached until shortly before expiry, and renewed automatically after the first call. Answers .unsupported without a request when no appKey was given. Never throws. Blocking — call it off the main thread. See Attest.

renewAttestation

func renewAttestation() -> Attestation

Registers again now, even while the current result is valid. A failure keeps the previous valid result. Blocking.

storedAttestation

func storedAttestation() -> Attestation?

The registration this install already holds, if it is still valid, read without a network request; nil when attest() would have to register again. Reads the keychain: call it off the main thread.

forgetAttestation

func forgetAttestation()

Forgets this install's registration and any wait after refused attempts, so the next attest() registers from the start. Keeps the device key. Stops the automatic renewal until the next attest().

lastAttestation

var lastAttestation: Attestation? { get }

The last attestation result, or nil before the first attest().

nextAttestationRefresh

var nextAttestationRefresh: Date? { get }

When the automatic renewal will next run. nil before the first attest(), and on a device that cannot keep a key.

Ads

iOS has no ad module like the Web's ads.session_opened(). It has the three calls below, which sign the current score into a proof for one ad event with the device key. Each one asks DEA for a single-use nonce before it signs, so each one blocks: call them off the main thread. Proofs carry registryAppId as their appId, unless DEA names the app when it issues the nonce. See Ads for the impression, click and end in order.

sendProof

func sendProof(
    event: ProofEvent,
    sessionId: String? = nil,
    attestation: Attestation? = nil,
    adUnit: String? = nil,
    appScopedUid: String? = nil,
    extra: [String: Any] = [:],
    update: Bool = false
) -> RecordOutcome

Signs one proof and sends it to DEA's recording service: POST /api/sessions, or PUT /api/sessions/{sessionId} with update: true.

Parameter Type Description
event ProofEvent The ad event, signed as event_type
sessionId String? The ad session, signed as session_id. "" for an impression, so DEA answers with a new id; that id for the click and the end. nil signs the SDK's own session id (sessionInfo.session), which is not an ad session
attestation Attestation? Decides only the tier signed into the proof: B for a passed, unexpired registration with platformAttested, C otherwise. nil uses the registration the SDK holds
adUnit String? The ad unit, signed as ad_unit
appScopedUid String? A pseudonymous id for the user, scoped to your app, signed as app_scoped_uid
extra [String: Any] Fields of your own, sent in the request body beside the proof, outside its signature. JSON values only. Never replaces the proof
update Bool Send as PUT /api/sessions/{sessionId}, the end of the ad session the impression opened

When DEA does not answer or answers with a 5xx, the SDK sends the proof again after 2, 4, 8 and 16 seconds (plus up to one second each), five sends at most, and never after the proof's 90-second lifetime. A 4xx answer is final. With every attempt failing, a call can block for about two minutes.

Returns: RecordOutcome. accepted is false, and said says why, when the classification is still .insufficient, there is no device key, the scoring engine is not running, there is no appKey (nothing is sent), DEA refused the proof, or no send got through. See Read why a proof was not recorded.

Throws: Never.

record

func record(
    event: ProofEvent,
    sessionId: String? = nil,
    attestation: Attestation? = nil,
    adUnit: String? = nil,
    appScopedUid: String? = nil,
    extra: [String: Any] = [:],
    update: Bool = false
) -> Bool

sendProof with the same parameters, answering only accepted: true when DEA accepted the proof. Blocking.

getProof

func getProof(
    event: ProofEvent,
    sessionId: String? = nil,
    attestation: Attestation? = nil,
    adUnit: String? = nil,
    appScopedUid: String? = nil
) -> SignedProof?

Signs one proof and returns it without sending it, for an app that delivers proofs its own way. Takes the parameters of sendProof. Still asks DEA for the nonce, so it blocks; without appKey, or when DEA gives no nonce, the proof is signed without one.

Returns: the proof, or nil when nothing was signed: the classification is still .insufficient, there is no device key, or the scoring engine is not running.

dispose

func dispose()

Stops capture, the model update checks and the automatic attestation renewal, and releases the SDK. A disposed instance cannot be started again; create a new one.

SwiftUI

deaConsent

@available(iOS 15.0, *)
func deaConsent(
    _ consent: Consent,
    asking: Binding<Bool>,
    copy: ConsentCopy = ConsentCopy(),
    remember: Bool = true,
    onAnswer: @escaping (Bool) -> Void
) -> some View

A View modifier. Shows the consent alert while asking is true, records the answer on consent, and calls onAnswer. It does not check for a stored answer: set asking only when consent.isAnswered is false.

Parameter Description
consent The SDK's consent
asking Whether the alert is shown
copy The alert's text
remember Store the answer. false keeps it for this run only
onAnswer Called with the user's answer

Types

DeaSdkOptions

Field Type Default Description
appId String — Your DEA appId
requireConsent Bool true Record nothing until the user agrees
consent Consent? nil (stored in UserDefaults) Where the consent answer is kept
appKey String? nil Your DEA appKey, sent as X-App-Key. Needed for Attest and Ads
registryAppId String? nil The appId written inside signed proofs. Set it to your appId for Ads
onLog ((LogEntry) -> Void)? nil Never called by the production build
modelDirectory URL? nil (Application Support) Where downloaded models are kept
modelUpdates Bool true Check for and download newer models
modelTimeout TimeInterval 30 Seconds one model request may take
events [CaptureEvent] CaptureEvent.pointerKinds Input kinds to capture
idleMotionMs Int 3000 Motion reading interval while nobody touches the screen
motionRate Int 50 Motion sensor rate to request, in Hz; at least 25
touchMarginMs Int 200 Full-rate motion around each touch, in ms

The remaining fields are reserved. See Options.

DeaSdkError

Deprecated and never thrown: init(options:) does not throw. A reserved integrations option set with a production appId is ignored.

Classification

Field Type Description
result ClassificationResult The answer
value Double? The human score, from 0 to 1. nil exactly when result is .insufficient
actions Int How many interactions value stands on. For "N more actions" while .insufficient
botThreshold Double? The loaded model's bot threshold. nil before a model loads
humanThreshold Double? The loaded model's human threshold. nil when the model sets none, and then every reading at or above botThreshold is .human
modelVersion Int? The model the thresholds come from; 0 for the built-in one. nil before a model loads

Classification.unloaded is the value returned before the scoring engine loads.

ClassificationResult

Checked in this order:

Case When
.insufficient The engine or model is not loaded, or the session has fewer actions than the model asks for (8 with the built-in model). value is nil
.human value at or above humanThreshold
.bot value below botThreshold
.human humanThreshold is nil, and value is at or above botThreshold
.undetermined value at or above botThreshold and below humanThreshold

The thresholds come from the loaded model only. Nothing in your account or in the SDK overrides them.

Migrating to this release: Classification.sufficient is gone, and ClassificationResult has a fourth case.

Before This release
c.sufficient == false c.result == .insufficient
c.value while not sufficient: a number nil
ClassificationResult has three cases Four: handle .insufficient in every switch
No humanThreshold, value at or above botThreshold: .undetermined .human

ThresholdMissing

An Error. Thrown by getClassification() when the loaded model carries no bot threshold.

Field Type Description
modelVersion Int The model that carries none

ModelInfo

Field Type Description
version Int The model's version; 0 for the model built into the SDK
source ModelSource .embedded (built in), .cache (downloaded earlier, read at launch) or .network (downloaded during this run)

ModelCheck

Field Type Description
outcome ModelCheckOutcome How the check ended — see below
refusal ModelLoad? What the scoring engine said, when it refused the file
detail String? Why the check failed or was cancelled
ModelCheckOutcome Meaning
.loaded A newer model was downloaded and is scoring now
.unchanged Nothing newer is published
.notPublished DEA has published no model this SDK can download
.notSupported A newer model is published, but this SDK version cannot read it
.notDue Less than a day since the last answered check, and force was false
.refused The scoring engine refused the downloaded file. The running model is unchanged
.failed No usable answer: offline, a timeout, an unexpected status, or a file that does not match the published size or checksum
.cancelled Model updates are not running, or the SDK was disposed

From DeaBase.

Member Type Description
decision ConsentDecision .unknown, .granted or .denied
isAnswered Bool Whether the user has answered
isGranted Bool Whether the user agreed
set(_:remember:) Void Records an answer. Same as DeaSdk.setConsent
forget() Void Clears the stored answer so the user is asked again
onChange(_:) () -> Void Calls the listener with the new decision when it changes; call the returned closure to stop
init(store:decision:) Builds a Consent over a ConsentStore — see Options
Consent.granted() Consent An already-granted answer, not stored

ConsentStore

From DeaBase. A protocol with read() -> ConsentDecision and write(_ decision: ConsentDecision). Implementations: DefaultsConsentStore (in UserDefaults, the default) and MemoryConsentStore (this run only).

ConsentCopy

Field Type Description
title String Alert title
body String Explanation
recorded [String] What is recorded; empty hides the section
notRecorded [String] What is never recorded; empty hides the section
recordedLabel String Heading for recorded
notRecordedLabel String Heading for notRecorded
closing String Closing sentence
accept String Accept button
decline String Decline button

Every field has an English default; pass only the ones you change.

SessionInfo

From DeaBase.

Field Type Description
session String Current session id, a UUID
device DeviceRecord Current device information
sdk.version String SDK version
sdk.core String Scoring engine build
startedAt Int When the session started, Unix milliseconds

DeviceRecord

From DeaBase.

Field Type Description
runtime Runtime .ios
os OperatingSystem Operating system
surface Surface Screen size in points (width, height)
pixelRatio Double Pixels per point
rotation Int Screen rotation: 0, 90, 180 or 270
maxTouchPoints Int Simultaneous touches the screen supports
sensorRate Int The motion rate requested, in Hz, once motion data has arrived; 0 until then

CaptureEvent

From DeaBase. The input kinds the events option lists: .pointerDown, .pointerMove, .pointerUp, .pointerCancel, .wheel, .keyDown, .keyUp. CaptureEvent.pointerKinds is the first five.

KeyClass

From DeaBase. The kind of key captureKey reports: .letter, .digit, .space, .delete, .navigation, .modifier, .other. KeyClass.of(_ character: String) classes one typed character as .letter (any script), .digit, .space or .other.

JSONValue

From DeaBase. A value in a captureEvent payload: .null, .bool, .int, .double, .string, .array([JSONValue]), .object([String: JSONValue]). String, integer, floating-point and Boolean literals convert automatically.

CoreCounts

Counted per session: reset() starts them again.

Field Type Description
samples Int Captured events that produced scored input
skipped Int Events accepted without producing scored input at that moment: custom events, lifecycle changes, scrolling and hover, and input held briefly to keep events in order
rejected Int Events refused. Above zero is a bug worth reporting to DEA
faults Int Internal errors the SDK recovered from
dropped Int Events skipped because the device could not keep up

Attestation

Field Type Description
supported Bool Whether a key could be created and an appKey was given
verdict Attestation.Verdict .passed, .failed or .unsupported
platformAttested Bool Whether an App Attest token went up with the key and DEA accepted it
platformTokenSpent Bool Whether this attempt used an App Attest attestation, whatever the result
expiresAt Int? Expiry, epoch seconds
kid String? Key identifier
tier String? Trust tier assigned by DEA
integrity String? DEA's reading of the device's integrity, when its answer carries one
backing String? Where the key is kept: secure_enclave or software
detail String? Why attestation did not pass

ProofEvent

The ad event a proof records, signed as event_type.

Case rawValue When
.adImpression ad.impression The ad is on screen
.adClick ad.click The ad was clicked
.adEnded ad.ended The ad is gone

RecordOutcome

What sendProof returns.

Field Type Description
accepted Bool Whether DEA accepted the proof
signed SignedProof? The proof that was signed, or nil when nothing was
isSigned Bool Whether a proof was signed
said String? What DEA answered, or why nothing was signed or sent. nil when DEA accepted the proof with an empty answer
sessionId String? The session_id in DEA's answer, or nil when it named none. An accepted impression carries the ad session's id here; send the click and the end with it

SignedProof

What getProof returns, and RecordOutcome.signed. The fields are copies of what was signed, read back from the proof.

Field Type Description
proof String The proof, a compact JWS: header.payload.signature
score Double The human score signed into it, h_score
evidenceAt Int When the evidence behind the score ends, epoch seconds, h_ts
windows Int How many actions the score stands on, h_wc
modelVersion Int? The model that scored, h_mv; 0 for the one built into the SDK
sessionId String The session_id signed into it
event ProofEvent The event signed into it
header [String: Any]? The decoded header: alg, typ, kid
claims [String: Any]? The decoded claims, as signed
signature String? The signature segment, base64url

sdkVersion

let sdkVersion: String

The SDK version, the same as sessionInfo.sdk.version.