ExtremeXCLIENT
SDK 3.2.0

Sign in. Ask permission. Save data.

Build Auth and Data into your app, one step at a time. Tour the platform, configure the requested data, then follow the guide for your language—with file locations, examples and expected results at each step.

ONE handles identity, login, consent and missing profile fields. Client is the developer workspace where you register and configure your app. The SDK connects your app to both.

Explore the platform · Go to your platform guide

Advanced reference · v1.0

Exact contracts, permissions and API evolution

Read the full profile and data contracts, null/error semantics, cross-platform patterns, security requirements and verification matrix. Future app metadata, legal documents, organisation and moderation methods are clearly labelled as proposals.

Open advanced API specification →

Understand the platform

A guided tour of ONE and Client

ExtremeX Client is your developer workspace: register applications, configure credentials and define the data they request. ExtremeX ONE owns the user’s identity, sign-in, consent and isolated app data. The SDK connects your application to those services.

Your appONE loginReview / add dataYour app callback

The callback carries a short-lived code and state. The SDK checks the callback and state, exchanges the code using its saved PKCE verifier, and then requests the allowed profile. Data requests use the resulting bearer token; they do not send the user’s ONE password.

1. Namespace and workspaces

Start in Dashboard. Your namespace is your company’s reverse domain, such as com.example. Registering the service myapp produces APP_ID com.example.myapp. Choose the intended workspace before creating an app; workspace access determines which controls you can use.

What this means: The namespace and app ID identify your integration. A namespace becomes permanent when apps are registered, keeping their app IDs and callbacks stable.

2. Overview

Open a registered app to see connected users, sign-ins, active refresh sessions and field-grant activity. Change the date range to inspect recent activity after testing a sign-in.

What this means: Use this to confirm that authorizations are reaching the intended app. A server-side authorization event is not proof that your device received its callback; check both.

3. Users

Inspect the recent users connected to this app and the fields they granted. Email visibility depends on whether that user granted the email field.

What this means: A missing optional field is not necessarily a bug. The user may have declined it or later revoked that permission.

4. Credentials

Copy APP_ID and CLIENT_ID, choose the app type, and inspect redirect URLs. Native clients receive an automatic HTTPS relay plus a custom URI scheme. Confidential web backends also use CLIENT_SECRET.

What this means: REDIRECT_URL is the URL sent in the authorization and code-exchange requests. CUSTOM_URI_SCHEME is only the scheme registered with the operating system. They are related but are not interchangeable.

5. Data access

Select the ONE profile fields the app needs. Mark each required or optional and explain its purpose. Enable App data to use the data API. Enable Stay signed in if the app should receive a refresh token.

What this means: This defines the consent screen. Required choices stay selected. The user can deselect optional choices; the app must handle their absence. Changing these settings does not silently add permission to existing tokens—run the consent flow again.

6. Create data

Declare the keys your application writes, their types and descriptions. For the tutorials, add theme as a string and high_score as a number. These are app-specific values, separate from ONE identity fields such as first_name or email.

What this means: Declaring a key defines its type; it does not create a value for every user. A missing key reads as null. Storage is isolated by app and user, even when two apps use the same key name.

7. Settings

Maintain the display name, description and other available app settings so users recognize the app on the consent screen. Review the app’s status before testing a new integration.

What this means: Keep the app identity recognizable. Developer metadata does not grant permission to read user data; the user still authorizes access through ONE.

8. ONE account and connected apps

ONE is the user-facing identity service. Users sign in there, maintain their profile and review connected apps. The login flow opens requested-data consent, then asks for selected profile fields that are missing.

What this means: Saving missing profile data is followed by another consent review. Local SDK sign-out clears the app’s stored session; revoking access from ONE’s Connected apps is a separate action.

The tab names above match the current Client UI. Team permissions may hide controls that your account cannot use.

Shared setup · every platform

Register and configure your application

Complete these steps once for the application you are integrating. The language-specific guide below assumes this registration already exists.

  1. Sign in to Client and choose a workspace

    Open Dashboard and sign in with ONE. On first use, claim your company’s reverse-domain namespace, such as com.example. Choose the workspace that should own this application.

    Expected result: you can create an app under the intended namespace. Existing team permissions determine which controls are available.

  2. Create the app and select its type

    Use a service name such as myapp or app.myapp and a recognizable display name. The namespace and service produce APP_ID=com.example.myapp. Choose Public client for native, desktop, React Native or browser-only apps. Choose Web app for a backend that can keep a secret, and provide its HTTPS callback.

    Why: app type determines how token exchange authenticates the client. Public clients use S256 PKCE; confidential web servers also use CLIENT_SECRET.

  3. Define the profile fields and permissions

    Open Data access. Select the ONE identity fields your app needs, explain their purpose and choose required or optional. Enable App data for the data examples. Enable Stay signed in to receive rotating refresh tokens.

    Expected result: the consent screen shows these fields and reasons. Optional fields may be declined, so your app must handle their absence.

  4. Declare your first app-data keys

    Open Create data. Add theme with type string and high_score with type number. Save the schema. These keys belong to your app; they are separate from ONE profile fields.

    Expected result: writes to declared keys are type-checked. Declaring a key does not initialize a value; it stays absent until your app writes it.

  5. Copy the public configuration and register the callback

    Open Credentials. Copy APP_ID and CLIENT_ID. Follow the credential table and your platform’s callback instructions below. Use the automatic native relay for the supplied Android/Apple clients, the direct custom callback for React Native AppAuth, or your registered HTTPS callback for a website/backend.

    Expected result: the URL used by your SDK matches an entry in Client exactly. Keep CLIENT_SECRET on confidential backends only.

ValueWhere to use it
APP_IDPublic identifier for your app. Pass as appId; determines the app’s data namespace and default native callback.
CLIENT_IDPublic OAuth identifier. Pass as clientId in every SDK.
CLIENT_SECRETConfidential web backends only. Keep in server environment variables. Omit from native apps, browser code and distributed desktop builds.
REDIRECT_URLExact registered callback URL. Pass as redirectUri for authorization and token exchange. Native apps get a hosted redirect automatically.
CUSTOM_URI_SCHEMENative apps: register the scheme alone (for example com.example.app) with the OS. The full callback is com.example.app://callback. Websites use their HTTPS callback instead.

Native apps default to https://client.extremextechnology.com/callback/APP_ID, which opens APP_ID://callback. Browser-only apps must register their own HTTPS callback as an additional redirect and set both redirectUri and callbackUri to it.

Build your integration

Choose a platform and follow the steps

Each guide covers installation, configuration, the login return path, data access and verification. Complete the shared registration steps above first.

Set up permissions before calling the SDK

  1. Register your app. In Dashboard → Register application, choose a display name, description and public HTTPS icon URL. A service suffix such as app.myapp under com.example produces com.example.app.myapp. Copy the generated APP_ID and CLIENT_ID exactly.
  2. Choose Requested data. Open the app’s Data access tab → Requested data. Enable only the profile fields you need and explain why. Enable App data to use one.data.set/get/del/rep. Creating a variable in Create data defines its schema; it does not grant permission. Mark optional preferences optional.
  3. Register the callback. Use the exact REDIRECT_URL shown in Credentials. On Android, use separate MAIN/LAUNCHER and VIEW/BROWSABLE intent filters; only the VIEW filter contains the custom URI scheme and callback host. Never bundle CLIENT_SECRET in native, mobile or browser apps.
  4. Sign in and review consent. The user must allow App data to sync app variables. Allowed registration scopes are not user consent. Changing Requested data does not upgrade old access or refresh tokens: sign in again and review the updated permission screen.
  5. Navigate after authentication succeeds. Profile or theme requests are separate operations. A theme error must not overwrite a successful sign-in with “login failed”. Handle each data call independently; users who decline optional App data can still use the signed-in app.
  6. Render only shared profile fields. Use named one.auth.data helpers. Null means missing or unshared; false is a valid verification/age result. Show no raw date of birth. Refresh on return from ONE so permission changes and profile updates take effect. SDK missing-data prompts let users complete fields they already agreed to share.
  7. Test the choices. Try a fresh login, restored session, denied optional App data, missing profile fields, revoked access, offline mode and guest access. Guests have no ONE profile or cloud-data permission. Never infer a verification result from a formatted phone number or a browser redirect.
KotlinKeep theme errors separate from successful sign-in
// Run after login succeeds. App data must be enabled AND consented.
one.data.get("theme", object : ExOneCallback<Any> {
    override fun onSuccess(value: Any?) {
        // Use the saved preference, or an app-local default when absent.
    }
    override fun onError(error: ExOneException) {
        when (error.code) {
            "insufficient_scope" -> { /* Offer sign-in again to review App data. */ }
            "access_revoked" -> { /* Access was withdrawn; stop data access. */ }
            "invalid_token" -> { /* Ask the user to sign in again. */ }
            else -> { /* Show a retry message for this data operation. */ }
        }
        // Do not mark a successfully authenticated user as a failed login.
    }
})
Why did I see insufficient_scope after login?

ONE successfully authenticated the user, then the app called its data endpoint without the attributes (App data) scope. Enable App data in Data access → Requested data, save, and sign in again. Do not add permissions to tokens yourself or skip server consent checks. If the user declines optional access, keep cloud preferences disabled.

Why are only first and last name displayed?

Those may be the only fields the app requested and the user allowed. Adding UI rows does not authorize additional data. Configure the required fields in Client and let the user review consent again.

Why is age or phone verification unavailable?

Age verification requires a configured provider with remaining free quota. ONE stores validated provider results, not client claims. A free sandbox or one-time trial is not an unlimited production allowance. Phone formatting or carrier lookup does not prove ownership; a trusted OTP verification must succeed before the phone is marked verified.

Android · Kotlin

Install method: Maven / Gradle

No bundle required. Gradle downloads the Android Auth/Data/UI artifact and its dependencies.

Optional source bundle · all platforms in one archive

Before you start

  • Android API 24+
  • An Android Studio project using Kotlin
  • Public client registered in Client

Files and configuration you will touch

  • settings.gradle.kts — ONE Maven repository
  • app/build.gradle.kts — exone-android dependency
  • AndroidManifest.xml — internet permission and callback
  • MainActivity.kt — layout, callback and data calls
Quick code preview

Use the steps below for file placement and callback setup. Uppercase identifiers are placeholders; application-specific functions are explained in the relevant step.

KotlinAndroid · Kotlin overview
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.TextView
import com.extremex.one.ExOneCallback
import com.extremex.one.ExOneException
import com.extremex.one.auth.ExOneUser
import com.extremex.one.android.ExOneAndroid

class MainActivity : Activity() {
    private lateinit var one: ExOneAndroid
    private lateinit var status: TextView

    private val result = object : ExOneCallback<ExOneUser> {
        override fun onSuccess(user: ExOneUser) {
            status.text = "Signed in — ready for app data"
        }
        override fun onError(error: ExOneException) {
            status.text = error.message
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        one = ExOneAndroid(applicationContext, "APP_ID", "CLIENT_ID")
        status = TextView(this)
        val layout = LinearLayout(this).apply {
            orientation = LinearLayout.VERTICAL
            addView(one.loginView(this@MainActivity))
            addView(status)
        }
        setContentView(layout)
        one.handleCallback(intent, result)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        one.handleCallback(intent, result)
    }
}
Profile helper examples for Android · Kotlin

All results may be null when missing or denied. See the profile helper reference below for permissions, bitmap types and completion handlers.

KotlinONE profile helpers
// Inside a worker/coroutine after sign-in. Every result may be null.
val isUserTeenResult = one.auth.data.isUserTeen()
val isUserLegalResult = one.auth.data.isUserLegal()
val isUserAdultResult = one.auth.data.isUserAdult()
val isUserAgeVarifiedResult = one.auth.data.isUserAgeVarified()
val isUserBirthdayResult = one.auth.data.isUserBirthday()
val isUserEmailVarifiedResult = one.auth.data.isUserEmailVarified()
val isPhoneNumberVerifiedResult = one.auth.data.isPhoneNumberVerified()
val isWorkEmailVarifiedResult = one.auth.data.isWorkEmailVarified()
val isWorkPhoneNumberVerifiedResult = one.auth.data.isWorkPhoneNumberVerified()
val getEmailResult = one.auth.data.getEmail()
val getCountryResult = one.auth.data.getCountry()
val getWorkEmailResult = one.auth.data.getWorkEmail()
val getWorkCountryResult = one.auth.data.getWorkCountry()
val getNationalityResult = one.auth.data.getNationality()
val getCountryOfBirthResult = one.auth.data.getCountryOfBirth()
val getNameResult = one.auth.data.getName()
val getPhoneNumberResult = one.auth.data.getPhoneNumber()
val getAddressResult = one.auth.data.getAddress()
val getWorkPhoneNumberResult = one.auth.data.getWorkPhoneNumber()
val getWorkAddressResult = one.auth.data.getWorkAddress()
val getLanguagesResult = one.auth.data.getLanguages()
val getAvatarResult = one.auth.data.getAvatar()
  1. Android Studio → settings.gradle.kts and app/build.gradle.kts

    Step 1: Install Auth, Data and Android views

    Merge the repository into your existing dependency repositories. Add exone-android to the app module and sync Gradle. Keep minSdk at 24 or higher.

    Why: The Android artifact brings in exone-data and exone-auth transitively, so you do not need three separate dependency declarations.

    Gradle · Kotlin DSLStep 1 example
    // settings.gradle.kts
    dependencyResolutionManagement {
        repositories {
            google()
            mavenCentral()
            maven { url = uri("https://client.extremextechnology.com/repo") }
        }
    }
    
    // app/build.gradle.kts — inside dependencies { ... }
    implementation("com.extremex.one:exone-android:3.2.0")

    Expected result: Gradle resolves com.extremex.one.android.ExOneAndroid and the shared auth/data classes.

  2. AndroidManifest.xml → your callback Activity

    Step 2: Register the return route

    Replace com.example.myapp below with your APP_ID. Merge the intent filter and attributes into the Activity that handles login. In Client, keep the generated HTTPS REDIRECT_URL.

    Why: ONE returns to the HTTPS relay, which opens APP_ID://callback. Android must know which Activity receives that URL. INTERNET permission enables token and data requests.

    XMLStep 2 example
    <!-- app/src/main/AndroidManifest.xml -->
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <uses-permission android:name="android.permission.INTERNET" />
        <application>
            <!-- Merge into your existing Activity; keep its launcher filter. -->
            <activity android:name=".MainActivity"
                      android:exported="true"
                      android:launchMode="singleTask">
                <intent-filter>
                    <action android:name="android.intent.action.VIEW" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <category android:name="android.intent.category.BROWSABLE" />
                    <data android:scheme="com.example.myapp"
                          android:host="callback" />
                </intent-filter>
            </activity>
        </application>
    </manifest>

    Expected result: The browser can return to your Activity after consent.

  3. MainActivity.kt

    Step 3: Create the client and mount the login layout

    Replace APP_ID and CLIENT_ID with the public values from Credentials. Use the complete Activity skeleton below, or adapt its initialization and callback methods into your existing Activity.

    Why: The native view opens ONE; the SDK stores the PKCE transaction and validates the return before reporting success. A client secret is not used.

    KotlinStep 3 example
    import android.app.Activity
    import android.content.Intent
    import android.os.Bundle
    import android.widget.LinearLayout
    import android.widget.TextView
    import com.extremex.one.ExOneCallback
    import com.extremex.one.ExOneException
    import com.extremex.one.auth.ExOneUser
    import com.extremex.one.android.ExOneAndroid
    
    class MainActivity : Activity() {
        private lateinit var one: ExOneAndroid
        private lateinit var status: TextView
    
        private val result = object : ExOneCallback<ExOneUser> {
            override fun onSuccess(user: ExOneUser) {
                status.text = "Signed in — ready for app data"
            }
            override fun onError(error: ExOneException) {
                status.text = error.message
            }
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            one = ExOneAndroid(applicationContext, "APP_ID", "CLIENT_ID")
            status = TextView(this)
            val layout = LinearLayout(this).apply {
                orientation = LinearLayout.VERTICAL
                addView(one.loginView(this@MainActivity))
                addView(status)
            }
            setContentView(layout)
            one.handleCallback(intent, result)
        }
    
        override fun onNewIntent(intent: Intent) {
            super.onNewIntent(intent)
            setIntent(intent)
            one.handleCallback(intent, result)
        }
    }

    Expected result: A Sign in with ONE button and Create an account link appear. The callback changes the status label.

  4. On the device → Sign in with ONE

    Step 4: Follow login, permission review and missing data

    Tap sign in. ONE asks the user to log in, then displays requested fields and reasons. If a selected profile field is missing, add it, save, review again and allow access. Use one.showLoginDialog(this) if you prefer a dialog entry point.

    Why: The library provides the native entry view; ONE hosts the actual login, consent and profile-completion screens. Signup opens the ONE signup page.

    Expected result: onSuccess runs only after the code exchange and profile request succeed.

  5. Your signed-in screen or onSuccess handler

    Step 5: Write and read the first value

    In Client, enable App data and declare theme as a string. Execute the example only after sign-in succeeds. Do not place the write unconditionally in onCreate.

    Why: The data calls use the session created by auth, and the async variants avoid blocking Android’s main thread.

    KotlinStep 5 example
    // Run after the sign-in callback succeeds.
    // Also: one.data.rep("theme", "light", callback).
    // update requires a non-null value; creates the key if missing.
    one.data.set("theme", "dark", object : ExOneCallback<Void> {
        override fun onSuccess(result: Void?) {
            one.data.get("theme", object : ExOneCallback<Any> {
                override fun onSuccess(value: Any?) {
                    runOnUiThread { status.text = "Theme: $value" }
                }
                override fun onError(error: ExOneException) {
                    runOnUiThread { status.text = error.message }
                }
            })
        }
        override fun onError(error: ExOneException) {
            runOnUiThread { status.text = error.message }
        }
    })

    Expected result: The status label displays Theme: dark.

  6. Emulator/device + ONE → Connected apps

    Step 6: Verify session restoration and revocation

    Sign in, save theme, close and reopen the app, and read the value again. Enable Stay signed in in Client to test refresh. Then revoke App data access in ONE and confirm the app handles a denied read.

    Why: ExOneAndroid stores tokens and pending sign-in state in Keystore-encrypted storage excluded from backup. Its handleCallback result is delivered on the main thread, but the shared data callbacks run on a background thread.

    Expected result: The app restores a valid session, and revoked access produces a visible error instead of stale data.

App variables and profile data

one.data.set saves app data; get reads it; del deletes it; rep replaces it with a non-null value (creating the key if missing). Existing update/remove aliases remain compatible.

Read profile data with the named one.auth.data helpers, such as getName() and getEmail(). Age checks mean 13+, 18+, and 25+ without exposing the birth date. Missing or denied values return null. isUserAgeVarified() offers a verification dialog when false; complete verification in ONE and retry. Authentication, network and revoked-access errors propagate. End the app session with one.auth.signOut().

KotlinApp data and profile API
// SDK 3.2.0. After sign-in; run blocking calls off the main thread.
Thread {
    try {
        one.data.set("theme", "dark")
        val value = one.data.get("theme")
        one.data.rep("theme", "light") // Non-null replacement
        one.data.del("theme") // Deletes this app's value

        val name = one.auth.data.getName()
        val verified = one.auth.data.isUserEmailVarified()
        val teen = one.auth.data.isUserTeen()   // 13+
        val legal = one.auth.data.isUserLegal() // 18+
        val adult = one.auth.data.isUserAdult() // 25+
        val phoneVerified = one.auth.data.isPhoneNumberVerified()
    } catch (error: com.extremex.one.ExOneException) {
        error.printStackTrace()
    }
}.start()
// set/get/rep/del also accept ExOneCallback for asynchronous calls.

Moderation errors show the saved reason. Java/Kotlin expose error.message and error.details; JavaScript exposes error.message, error.code, and error.banExpiresAt. Render reasons as plain text. The Users tab configures app-scoped bans and JSON rules; no client-side enforcement is required for ONE API requests.

Platform troubleshooting

The browser never returns to the app

Compare the manifest scheme with APP_ID exactly; use host callback. Keep the HTTPS relay as REDIRECT_URL. Reinstall after changing the manifest.

A new Activity opens but login does not finish

Handle the incoming Intent in both onCreate and onNewIntent. Pass the new Intent itself to handleCallback.

Data calls return 403

Enable App data in Client, then sign in and approve again. Check that the user has not revoked access.

Network/UI thread error

Use data async methods for network operations and runOnUiThread for UI updates inside their callbacks.

Signup opens ONE signup. The native entry layouts launch ONE’s hosted login, permission and missing-profile screens. See release verification notes for platform build/device-test coverage.

Read the ONE profile safely

  1. Request access. In Client → Data access, select the fields your app needs and explain why. Age and birthday checks use the existing date_of_birth permission identifier, but its value is never returned.
  2. Ask the user. Sign in through ONE and let the user review access. Optional fields can be denied. Do not interpret denial as false.
  3. Read after sign-in. Use one.auth.data helpers. Each call reads current consent. Use a worker/coroutine or callback on UI platforms.
  4. Handle three results. True means the check passed; false means the known value did not pass; null means missing or ungranted data. A revoked session, ban or network failure is an error, not a null result.
  5. Complete missing fields. For granted-but-missing fields, the SDK opens or offers ONE’s existing completion view. Save in ONE, return to your app, then call the helper again. A denied field never triggers this prompt.
one.auth.data helperPermissionResultMeaning
isUserTeen()date_of_birthBoolean or nullAge 13 or older.
isUserLegal()date_of_birthBoolean or nullAge 18 or older.
isUserAdult()date_of_birthBoolean or nullAge 25 or older.
isUserAgeVarified()date_of_birthBoolean or nullTrusted ID, liveness and face-match approval; self-entered dates never set this flag.
isUserBirthday()date_of_birthBoolean or nullBirthday is today using the UTC calendar.
isUserEmailVarified()emailBoolean or nullRequires the matching live consent. Verification flags are false for a present, unverified value.
isPhoneNumberVerified()phoneBoolean or nullRequires the matching live consent. Verification flags are false for a present, unverified value.
isWorkEmailVarified()work_emailBoolean or nullFalse until the saved work email is verified.
isWorkPhoneNumberVerified()work_phoneBoolean or nullFalse until the saved work phone is verified.
getEmail()emailString or nullRequires the matching live consent. Verification flags are false for a present, unverified value.
getCountry()addressString or nullHome address country code.
getWorkEmail()work_emailString or nullRequires the matching live consent. Verification flags are false for a present, unverified value.
getWorkCountry()work_addressString or nullWork address country code.
getNationality()nationalityString or nullRequires the matching live consent. Verification flags are false for a present, unverified value.
getCountryOfBirth()country_of_birthString or nullRequires the matching live consent. Verification flags are false for a present, unverified value.
getName()first_name, last_nameString array or null[first_name, last_name]; an ungranted or missing component is null.
getPhoneNumber()phoneString array or null[country_code, phone_number], both strings; e.g. ["39", "0212345678"]. No + prefix in country_code.
getAddress()addressString array or null[house/apt/building, street, town/city, county/state/province, country, postcode/ZIP].
getWorkPhoneNumber()work_phoneString array or null[country_code, phone_number], both strings.
getWorkAddress()work_addressString array or nullSame six fixed positions as getAddress().
getLanguages()languagesString array or nullSaved language names.
getAvatar()avatarBitmap or nullDecoded ONE profile photo, at most 256 × 256 pixels.

Name and address arrays preserve their positions with null for an unavailable component. Country values are the saved ISO country code, or OTHER. Age thresholds use the saved date, regardless of verification; combine a threshold with isUserAgeVarified() when your app needs a verified age. No helper returns a birth date or exact age.

Verify age when requested

When isUserAgeVarified() returns false, the supplied UI offers a “Verify your age” dialog linking to https://one.extremextechnology.com/account. Open Age verification in ONE, complete the ID and face checks, return, then retry the helper. The dialog is offered once per SDK client session; null or denied access never opens an age-verification prompt. Missing permitted data still opens the completion prompt first.

Bitmap types by platform

Android returns Bitmap; iOS returns UIImage; macOS returns NSImage; browsers return ImageBitmap; Go returns image.RGBA. Java returns AvatarBitmap with width, height and ARGB pixels. KMP, Python, Ruby, Rust, Node and React Native return a decoded RGBA bitmap with width, height and pixel bytes. Adapt those pixels to your UI toolkit. No additional profile image files are stored by ONE.

Connect a completion prompt to your UI

Browser, Android and Apple clients include a prompt. Android apps should call one.attach(activity) after activity recreation. The supplied Swing and Tkinter views install their prompt handlers; Compose installs its handler while OneLoginView is mounted. React Native’s createNativeClient installs an Alert prompt. For headless clients or custom layouts, use the following hooks and open the supplied ONE URL after the user chooses to continue:

  • Java: one.auth.data.onMissingData; KMP: one.auth.data.onMissingData; Swift: one.auth.data.onMissingData.
  • JavaScript: createClient({onMissingData: (event) => …}) or one.auth.data.setMissingDataHandler(handler).
  • Python/Ruby: one.auth.data.on_missing_data; Go: one.Auth.Data.OnMissingData; Rust: one.on_missing_data.

Headless server code returns null and does not open a desktop browser. Forward the completion event through your authenticated UI, or provide your own dialog. For the age-verification action, the same handler receives fields ["age_verification"] and the fixed account URL; show “Verify your age” instead of a missing-data message. Handlers otherwise receive the missing fields and completion URL; JavaScript receives an event object with fields and url. JVM callbacks run on a worker; dispatch UI updates to the UI thread.

KotlinKotlin: nullable age checks (inside a worker or coroutine)
val legal: Boolean? = one.auth.data.isUserLegal()
val verified: Boolean? = one.auth.data.isUserAgeVarified()
when {
    legal == null || verified == null -> showMissingOrDeniedState()
    legal && verified -> continueWithVerifiedAdult()
    else -> showAgeRequirement()
}
val phone = one.auth.data.getPhoneNumber() // ["44", "7911123456"] or null
// The three UI functions above belong to your app.

Upgrade from SDK 1.x or 2.x

The generic one.auth.data.get and the old isUserVarified alias are removed. Use getName(), getEmail(), or another helper from the table. Sign-in/session support remains available for the login views. Use one.auth.signOut() to end the app session. Install version 3.2.0 using your platform’s normal install command below. Recompile and handle nullable flags: Java Boolean, Kotlin Boolean?, Swift/Objective-C NSNumber?, Go *bool, Rust Option<bool>, and null/None/nil in dynamic languages. Swift uses number.boolValue after unwrapping. Older release files remain available, but raw birth dates are removed from the server for every SDK version.

Go exports GetName/IsUserTeen and the other helpers with capitals; Rust uses get_name/is_user_teen and snake_case equivalents. Other platforms keep the spellings shown in the table, including Varified for compatibility.

How login, consent and views fit together

The Android library includes loginView and showLoginDialog. The source bundle includes SwiftUI, UIKit, Compose Multiplatform, React Native, Swing, Tkinter and browser views. Mount a layout or present it as a dialog/sheet.

  1. Login: the control opens ONE in the system authentication browser. “Create an account” opens one.extremextechnology.com/signup.
  2. Allow data access: ONE shows the app’s name, requested fields, reasons and required/optional choices. The user can decline optional fields or cancel.
  3. Add missing data: selected fields missing from the ONE profile are collected before consent. The user saves, reviews the request again, then allows access.

These consent and completion views are hosted by ONE and shared across platforms. They are opened by the library’s auth session; apps never collect the user’s ONE password or imitate the consent decision locally.

Android callback manifest
XMLAndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<!-- On your callback Activity (deliver onCreate and onNewIntent to the SDK): -->
<activity android:name=".MainActivity" android:exported="true"
          android:launchMode="singleTask">
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="YOUR_CUSTOM_URI_SCHEME" android:host="callback" />
  </intent-filter>
</activity>
Apple and desktop callbacks

Apple: register CUSTOM_URI_SCHEME under Xcode → Info → URL Types. Windows: register a URL protocol handler. Linux: use a desktop entry with x-scheme-handler/CUSTOM_URI_SCHEME. Route callbacks to the same user’s running client instance. For web servers, use a per-user session store that keeps pending state/verifier across requests.

Understand app data and sessions

data.set(key, value), data.get(key), data.rep(key, value) and data.del(key) operate on the signed-in user’s data within your app’s namespace. Declared types are checked by ONE. Access requires the user’s current App data consent.

Accepted values: JSON strings, numbers, booleans, null, arrays and objects. Values are compressed with DEFLATE, then encrypted with AES-256-GCM. Existing encrypted records remain readable. Limits: 16 KiB of uncompressed UTF-8 JSON per value, 100 keys per write, 20 levels of nesting, and a 1.7 MB request body.

No file-upload or media-storage API is provided. Multipart uploads, data URLs, recognizable encoded files and binary byte arrays are rejected. Do not encode videos, images or files into data fields. The only image-upload exception is the user’s ONE profile photo.

Tokens last one hour; optional Stay signed in enables rotating refresh tokens. Persist each replacement refresh token in secure storage. Local sign-out clears the app’s session; users revoke access from ONE → Connected apps.

Verify your integration step by step

  1. Start signed out. Open the app’s login control. Confirm ONE shows the intended application name and requested fields.
  2. Test cancellation. Cancel the request. Your app should display a signed-out or cancelled state and should not enable data operations.
  3. Exercise missing data. Request a profile field that your test profile has not filled. Select it, complete it in ONE, then review and allow access. An unselected optional field should not block authorization.
  4. Complete a successful callback. Verify the SDK receives the full callback URL exactly once and the app switches to its signed-in screen.
  5. Round-trip a value. Save theme = "dark", read it back, then delete it and confirm the missing-key result. Check that another app or user has a separate namespace.
  6. Test session lifetime. Relaunch or reload with the supported secure storage configured. If Stay signed in was granted, confirm your storage saves replacement refresh tokens.
  7. Revoke access. Disconnect the app or revoke the relevant field from ONE’s Connected apps. The next protected request should fail clearly; your app should not quietly display stale privileged data.

Use your registered identifiers in a real target build. The release verification notes distinguish compiled SDK checks from platform/device behavior still requiring your validation.

Installation and libraries

The full bundle is not required for every integration. Android and JVM projects resolve dependencies with Gradle/Maven. Websites can import the hosted module. Python and Ruby use a single file; other source-based platforms can use the relevant files or folder listed in their guide.

Maven repository · Integration notes

Gradle · Kotlin DSLGradle dependencies
repositories { maven { url = uri("https://client.extremextechnology.com/repo") } }
// Android: Auth + Data + layout/dialog + encrypted persistence
implementation("com.extremex.one:exone-android:3.2.0")
// Other JVM applications: Auth + Data
implementation("com.extremex.one:exone-data:3.2.0")

Optional source bundle · all platforms in one archive

The Java, Android, browser, Python, Ruby, Go and Rust libraries share the same flow. The KMP and Apple sources are included for platform builds; review the verification notes in the download before shipping on devices.

API reference

Authorization: https://one.extremextechnology.com/oauth/authorize
API base: https://api.extremextechnology.com/one

EndpointUsage
GET /oauth/authorizeBrowser flow: client_id, redirect_uri, state, scope and S256 code_challenge. This URL uses the ONE authorization host above.
POST /oauth/tokenJSON or form: grant_type=authorization_code, client_id, code, redirect_uri, code_verifier. Confidential web apps also send client_secret. Refresh uses grant_type=refresh_token, client_id and refresh_token.
GET /oauth/userinfoBearer token. Returns only fields still granted by the user.
GET /data?key=themeBearer token. Returns {key, value}; omit key to list data.
PUT /dataBearer token and application/json body: {"data":{"theme":"dark"}}.
DELETE /data?key=themeBearer token. Removes one value.

Errors: 401 requires a valid session or fresh sign-in; 403 indicates missing or revoked access; 400 indicates invalid input; 413 means the request is too large; 415 requires JSON. Do not automatically retry a consumed authorization code or reuse a rotated refresh token.

Native sign-in follows OAuth for Native Apps: an external browser, S256 PKCE, callback validation and no embedded client secret.