Human Scoring¶
Read whether the current session comes from a real person at the moment your app makes a decision. The SDK keeps the classification up to date as the user interacts, so reading it does not call the network: it waits for input the app has already received to be scored — usually nothing — and reads the result.
How the score, the evidence behind it and the model's thresholds fit together is explained in How scoring works.
In this use case¶
| Page | What it covers |
|---|---|
| Human Scoring (this page) | Read the classification at a decision point |
| User Consent | Ask the user for permission before anything is recorded |
| Sensitive Screens | Pause capture while a password or payment field is on screen |
| Custom Events | Mark moments in your app on the session timeline |
| Sessions | Start a new session on logout or account switch |
When to use it¶
- Before creating an account, submitting a form, or redeeming a promotion
- Before a step that bots abuse: posting, voting, adding to cart, claiming a reward
- To decide which branch of your own flow to take — let the user through, or add a challenge
Prerequisites¶
- The SDK is installed and started. See the Quick Start.
- The user has consented. Without consent nothing is recorded and the result stays
.insufficient. See User Consent. - The user has interacted with the app. Until the session has as many
actions as the model asks for — 8 with the built-in
model — it is not sufficient and the result is
.insufficient. Right after launch there are none. See Evidence.
Usage¶
Read the classification¶
import DeaSDK
let result = (try? Dea.sdk?.getClassification())?.result ?? .insufficient
switch result {
case .human:
allowAction()
case .bot:
refuseAction() // or your strictest fallback
case .undetermined:
showChallenge() // not certain either way
case .insufficient:
showChallenge() // not enough evidence yet: too early, not a bot
}
Replace allowAction, refuseAction and showChallenge with your own.
There are four answers, not two:
| Result | When |
|---|---|
.insufficient |
Not enough evidence yet, or the SDK cannot score. value is nil |
.human |
The score is at or above the model's human threshold |
.bot |
The score is below the model's bot threshold |
.undetermined |
The score is between the two thresholds |
A model that sets no human threshold has one bar: a score at or above its bot threshold is
.human. The model built into the SDK sets both thresholds.
What each result allows is your decision. Neither .insufficient nor .undetermined is a negative
classification: a user who has just opened the app reads .insufficient until the session has 8
actions, with the built-in model.
Show progress, or explain the result¶
import DeaSDK
guard let dea = Dea.sdk else { return }
do {
let reading = try dea.getClassification()
if reading.result == .insufficient {
print("\(reading.actions) action(s) so far; keep going")
} else {
print(reading.result, reading.value ?? 0, reading.botThreshold ?? 0,
reading.humanThreshold ?? .nan)
}
} catch let missing as ThresholdMissing {
print("model \(missing.modelVersion) carries no bot threshold")
} catch {}
| Field | Type | Description |
|---|---|---|
result |
ClassificationResult |
.human, .bot, .undetermined or .insufficient |
value |
Double? |
The score, from 0 to 1. nil exactly when result is .insufficient |
actions |
Int |
How many scored actions the score stands on; the result leaves .insufficient once there are as many as the loaded model asks for, 8 for the built-in model |
botThreshold |
Double? |
The loaded model's bot threshold |
humanThreshold |
Double? |
The loaded model's human threshold; nil when the model sets none |
modelVersion |
Int? |
The model the thresholds come from; 0 for the built-in one |
The thresholds come from the loaded model only: the one built into the SDK, or a newer one the SDK downloaded (see Model options). All the fields come from the same moment, so the result always agrees with the numbers beside it.
getClassification() throws ThresholdMissing only when the loaded model carries no bot
threshold. Every model DEA ships carries one; see Troubleshooting.
Check that the SDK can score¶
import DeaSDK
if let dea = Dea.sdk, !dea.ready {
print("DEA cannot score: \(dea.coreFault ?? "unknown")")
}
When ready is false, the SDK still runs and captures but cannot score, and
coreFault says why. getClassification() then returns .insufficient with value nil, and
does not throw.
Report typing¶
A phone's keyboard gives your text field characters, not key events, so the SDK cannot capture
typing by itself. Report each keystroke with captureKey, passing only its class — never the
character:
import DeaBase
import DeaSDK
Dea.sdk?.captureKey(KeyClass.of("a")) // .letter
Dea.sdk?.captureKey(.delete) // backspace
- Add
.keyDowntoevents. Without it,captureKeydoes nothing. - Never report keystrokes in a password field, not even as classes: the rhythm of typing alone can narrow a guess at a password.
KeyClass.of(_:)returns.letter(any script),.digit,.spaceor.other. Report deletions as.deleteyourself.- A soft keyboard has no key release; send the press alone. Where your app does see a release (a
hardware keyboard), report it with
pressed: falseand add.keyUptoevents. - Key presses are refused, like touches, while capture is paused and before the user consents.
Examples¶
Gate a sign-up button (SwiftUI)¶
import DeaSDK
import SwiftUI
struct SignUpButton: View {
let onSignUp: () -> Void
let onChallenge: () -> Void
var body: some View {
Button("Create account") {
if (try? Dea.sdk?.getClassification())?.result == .human {
onSignUp()
} else {
onChallenge() // e.g. show a captcha
}
}
}
}
Gate a sign-up button (UIKit)¶
import DeaSDK
import UIKit
class SignUpViewController: UIViewController {
@IBAction func createAccountTapped(_ sender: UIButton) {
if (try? Dea.sdk?.getClassification())?.result == .human {
submitSignUp()
} else {
showChallenge() // e.g. show a captcha
}
}
private func submitSignUp() { /* your sign-up */ }
private func showChallenge() { /* your fallback */ }
}
Report typing in a text field (UIKit)¶
Create the SDK with the key kind added:
import DeaBase
import DeaSDK
let dea = DeaSdk(
options: DeaSdkOptions(
appId: "YOUR_APP_ID",
appKey: "YOUR_APP_KEY",
events: CaptureEvent.pointerKinds + [.keyDown]
)
)
Then report from the field's delegate:
import DeaBase
import DeaSDK
import UIKit
class CommentViewController: UIViewController, UITextFieldDelegate {
private let commentField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
commentField.delegate = self
view.addSubview(commentField)
}
func textField(
_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String
) -> Bool {
if string.isEmpty {
Dea.sdk?.captureKey(.delete)
} else if string.count == 1 {
Dea.sdk?.captureKey(KeyClass.of(string))
}
return true
}
}
Pasted text and autocorrect replacements arrive as several characters at once; this example does not report them as typing.
API¶
| Member | Returns | Description |
|---|---|---|
getClassification() |
Classification |
The result, the score and the thresholds it was compared with, in one reading. Throws ThresholdMissing |
ready |
Bool |
Whether the SDK is able to score |
coreFault |
String? |
Why the SDK cannot score, if it cannot |
captureKey(_:pressed:mods:repeat:field:) |
Void |
Reports one keystroke by its class. Needs .keyDown in events |
Related¶
- User Consent — nothing is scored before consent
- API Reference