Android Representations#

The Curity HAAPI Android SDK delivers every HAAPI response as a typed Kotlin value. HaapiResponse is the sealed root returned by HaapiManager.start, submitForm, and followLink; concrete subtypes — HaapiRepresentation for step-shaped responses and ProblemRepresentation for RFC 7807 errors — tell you what the server is asking the client to do.

The same model hierarchy exists on iOS and React Native — see iOS Representations and React Native Representations for the per-platform shape. This page is the Android narrative reference.

SDK-thrown errors are separate. Network failures, attestation failures, and other framework errors arrive through the coroutine’s Throwable channel (or as failed Result values), not as a HaapiResponse variant. Catch them with try/catch and cast to HaapiError for the typed hierarchy. See Error Handling.

Response Hierarchy#

HaapiResponse is a sealed Kotlin interface with two sealed children:

SiblingWhen it surfacesImplemented by
HaapiRepresentationThe flow continues with another step14 concrete step classes (8 HaapiRepresentations + 6 ClientOperationStep siblings)
ProblemRepresentationThe server rejected the request (RFC 7807)Problem, InvalidInputProblem, AuthorizationProblem

Branch with a when expression on the response, then narrow on the concrete step or problem class.

Step Types#

HaapiRepresentation is the sealed interface for renderable steps. A parallel ClientOperationStep sealed interface captures steps that delegate to an external operation (BankID, WebAuthn, external browser, …).

Common fields#

Every step exposes these base fields:

FieldTypeDescription
typeRepresentationTypeHAAPI representation type (8 values)
metadataMetadata?Template area and view name
actionsList<Action>Available actions for this step
linksList<Link>Navigation links (e.g., “forgot password”)
messagesList<UserMessage>User-facing messages to display
propertiesProperties?Step-specific properties

The 14 step types#

Grouped by purpose:

GroupConcrete classExtra fields
InteractiveAuthenticatorSelectorSteptitle: Message, authenticators: List<AuthenticatorOption>
InteractiveFormStep(form actions inside actions)
UserConsentStep—
Flow controlRedirectionStepredirectAction: Action
PollingStepmainAction, cancelAction?, properties: PollingProperties
ContinueSameStep—
OAuthOAuthAuthorizationResponseStepproperties: OAuthAuthorizationResponseProperties (carries code)
Client operation (implement ClientOperationStep)ExternalBrowserClientOperationStep—
BankIdClientOperationStepactivationLink?
EncapClientOperationStep—
WebAuthnRegistrationClientOperationStep—
WebAuthnAuthenticationClientOperationStep—
GenericClientOperationStep—
FallbackGenericRepresentationStepproperties?

Branching on the result#

import se.curity.identityserver.haapi.android.sdk.models.*
import se.curity.identityserver.haapi.android.sdk.models.actions.*

val response: HaapiResponse = haapiManager.start()

when (response) {
    is HaapiRepresentation -> handleRepresentation(response)
    is ProblemRepresentation -> handleProblem(response)
}

private fun handleRepresentation(representation: HaapiRepresentation) {
    when (representation) {
        is AuthenticatorSelectorStep -> {
            // representation.title.literal, representation.authenticators
            // Each AuthenticatorOption carries an Action.Form for selection.
        }
        is InteractiveFormStep -> {
            // representation.actions contains form actions;
            // render their fields as a Compose / View form.
        }
        is PollingStep -> {
            // representation.properties.status, representation.mainAction, representation.cancelAction
        }
        is OAuthAuthorizationResponseStep -> {
            // Flow complete — extract the authorization code.
            val code = representation.properties.code
        }
        is ClientOperationStep -> {
            // Delegate to the platform (BankID, WebAuthn, external browser, …)
        }
        else -> {
            // GenericRepresentationStep, ContinueSameStep, UserConsentStep, RedirectionStep.
        }
    }
}

Actions#

Actions represent operations the user can perform. They live under the sealed Action class:

SubclassCarriesWhen to use
Action.Formmodel: FormActionModelThe most common action — render fields, submit values
Action.Selectormodel: SelectorActionModel (nested options as List<Action>)Server provides a choice (e.g., device selector); each option is itself an Action
Action.ClientOperationmodel: ClientOperationActionModelExternal handling required (BankID, WebAuthn, browser launch)

Every action also carries:

FieldTypeDescription
kindActionKindSemantic meaning: Login, Cancel, Redirect, Poll, etc. (17 values)
titleMessage?Display title for buttons / labels

Action.Form#

Submitting a form is the SDK’s primary write operation:

val formAction: Action.Form = …

val parameters: Map<String, Any> = mapOf(
    formAction.model.fields[0].name to "user@example.com",
    formAction.model.fields[1].name to "secret"
)

val next = haapiManager.submitForm(formAction, parameters)

submitForm’s parameters accept any JSON-serialisable value (String, Number, Boolean, nested maps, lists) for non-GET methods; GET form actions require Map<String, String>. See HAAPI Flow for the full parameter-type semantics across platforms.

The FormActionModel carries:

FieldTypeDescription
fieldsList<FormField>Form fields to render
methodStringHTTP method (e.g., "POST")
hrefStringTarget URI
actionTitleMessage?Submit button text

Action.ClientOperation#

Six sub-types of ClientOperationActionModel, each with its own fields: ExternalBrowser, BankId, Encap, WebAuthnRegistration, WebAuthnAuthentication, Generic. Each model includes continueActions and errorActions lists for post-operation flow.

Form Fields#

FormField is a sealed Kotlin class with 7 subclasses:

Common fields#

FieldTypeDescription
nameStringParameter key for submission
valueString?Current / default value
labelMessage?Display label
placeholderMessage?Placeholder text
isRequiredBooleanWhether the field is required

Rendering guide#

FormField subclassCompose / View elementNotes
FormField.HiddennoneInclude value in submit params automatically — don’t render
FormField.TextTextField / EditTextInspect kind for keyboard type (Email, Tel, Number, Url, Color)
FormField.UsernameTextField / EditTextDisable autocapitalisation
FormField.PasswordTextField / EditTextvisualTransformation = PasswordVisualTransformation()
FormField.SelectDropdownMenu / SpinnerRender options as choices
FormField.CheckboxSwitch / CheckboxRespect readonly flag
FormField.ContextnoneHidden context data (e.g., WebAuthn challenge) — don’t render

Problems#

Problem responses follow RFC 7807. Android exposes them as concrete ProblemRepresentation subclasses:

Classtype (ProblemType)Extra fields
Problembase value(base only)
InvalidInputProblemInvalidInputProbleminvalidFields: List<InvalidField> (each: name, reason?, detail: Message), errorDescription?
AuthorizationProblemErrorAuthorizationResponseProblemerror, errorDescription?, iss?

InvalidInputProblem.invalidFields is the right hook for showing field-level validation errors inline in your form.

Token Responses#

OAuthTokenManager.fetchAccessToken, refreshAccessToken, and revokeRefreshToken return a TokenResponse sealed interface. Branch its variants:

VariantFields
SuccessfulTokenResponseaccessToken, tokenType?, scope?, expiresIn, refreshToken?, idToken?
ErrorTokenResponseerror, errorDescription?, errorUri?

SDK-thrown errors (network, attestation, framework) come back through the coroutine’s exception channel, not as a TokenResponse variant. See OAuthTokenManager for the full lifecycle, and Error Handling for the difference between RFC-6749 OAuth errors (values) and framework errors.

Each Link carries:

FieldTypeDescription
hrefStringTarget URI
relStringRelationship type (e.g., forgot-password)
titleMessage?Display text
typeString?Hint for the media type of the linked resource

Render link sets alongside the main form — typically as secondary buttons or text links.

Classify before acting#

Not every link is a HAAPI step. Call link.kind(baseUri) and act on the result rather than following every link:

LinkKindMeaningWhat to do
HAAPIA HAAPI resourcehaapiManager.followLink(link)
INLINE_IMAGEAn image, such as a BankID QR codeRender it in place; never follow it
EXTERNALContent for the platformHand href to the browser or another app
when (link.kind(baseUri)) {
    LinkKind.HAAPI -> haapiManager.followLink(link)
    LinkKind.INLINE_IMAGE -> render(link.href)   // e.g. a data: QR image
    LinkKind.EXTERNAL -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.href)))
}

kind is pure and deterministic: it performs no network I/O and reads only the link plus baseUri, which is used to resolve a relative href before its scheme is examined. Media types are compared normalized — lower-cased, parameters after ; stripped. The rules are evaluated in order:

  1. a type of image/* is INLINE_IMAGE;
  2. a type of text/html is EXTERNAL;
  3. a rel of download is EXTERNAL;
  4. a blank href, or a resolved scheme other than http/https, is EXTERNAL;
  5. any other declared type is EXTERNAL;
  6. otherwise — including when type is absent — it is HAAPI.

A link with no type is a HAAPI resource, per the HAAPI data model, and must be fetched with an authenticated request. Do not treat a missing type as a reason to compare the link’s origin against your base URL.

Classification never decides whether the target is really your identity server; HaapiManager.followLink enforces that at follow time.

The iOS equivalent is a parameterless link.kind property with a slightly different rule table — it folds text/html into the general “any other declared type” rule and also treats authorization-response as an external relation. Do not assume the two are call-compatible; see iOS Representations.

Related: HAAPI Flow · OAuthTokenManager · Error Handling · Android SDK · Android Reference (KDoc for the full type signatures)

Was this helpful?