Skip to content

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 .keyDown to events. Without it, captureKey does 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, .space or .other. Report deletions as .delete yourself.
  • 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: false and add .keyUp to events.
  • 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