API Reference¶
There is no shared instance: create one DeaSdk and keep it. See the
Quick Start.
DeaSdk¶
init(options:)¶
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¶
Starts global capture. Call it once, on the main thread, as early as possible. Does nothing after
dispose().
options¶
The options this instance was created with.
appKey¶
The app key the SDK sends as X-App-Key: the appKey option,
trimmed, or nil when none was given.
ready¶
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¶
Why the SDK cannot score, or nil.
getClassification¶
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¶
Waits until input the app has already received has been scored. getClassification() does this
itself.
modelInfo¶
Which model is scoring now: its version (0 for the model built into the SDK) and where it came
from. See Options.
checkForModel¶
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¶
Calls listener, off the main thread, each time a newer model starts scoring during this run.
Call the returned closure to stop listening.
consent¶
The user's current consent answer. See User Consent.
setConsent¶
| 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¶
Stops recording. The session is kept. See Sensitive Screens.
resume¶
Starts recording again after pause(). Does not override consent.
setMotion¶
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¶
| 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¶
Ends the current session and starts a new one. A model the SDK downloaded stays in use. See Sessions.
sessionInfo¶
The current session id, device and SDK version.
coreCounts¶
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¶
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¶
Registers again now, even while the current result is valid. A failure keeps the previous valid result. Blocking.
storedAttestation¶
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¶
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¶
The last attestation result, or nil before the first attest().
nextAttestationRefresh¶
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¶
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.sufficientis gone, andClassificationResulthas a fourth case.
Before This release c.sufficient == falsec.result == .insufficientc.valuewhile not sufficient: a numbernilClassificationResulthas three casesFour: handle .insufficientin everyswitchNo humanThreshold,valueat or abovebotThreshold:.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 |
Consent¶
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¶
The SDK version, the same as sessionInfo.sdk.version.