All Products
Search
Document Center

Alibaba Cloud Model Studio:HappyOyster Android SDK API Reference

Last Updated:Sep 18, 2026

The entry point for the HappyOyster Android SDK is the singleton object HappyOyster . Except for initialize , updateToken , attachVideo , and sendCommand , all business methods are suspend functions that throw SDKError on failure. Covers the three modes adventure / directing / acting, the required model config, and aspectRatio.

If the caller coroutine is cancelled, the SDK cancels that call's in-flight HTTP request, if any, and propagates CancellationException unchanged rather than converting it to SDKError. Cancellation does not roll back a request that the server has already accepted.

For the integration flow, installation, and best practices, see Happy Oyster Android SDK Integration Guide.

Core Terminology

Term

Meaning

token

Bailian gateway API Key: injected as a Bearer token by your app via updateToken(token). The SDK keeps only the latest one; it does not persist or refresh it. When the Key changes, you inject it again. The gateway requires all SDK requests, including startTravel, to use this Bearer. When the Bearer Key is expired or invalid, the SDK throws SDKError(101002); in that case you should re-obtain and inject the Bearer Key (updateToken), rather than exchange for a new ticket. Security recommendation: the client-side Bearer should use a short-lived token minted by your server, rather than bundling a long-lived API Key into the app or committing it to a code repository.

ticket

One-time experience credential: exchanged by your server via the open platform and delivered to the client, used only for a single startTravel. It becomes invalid once the experience ends (normally or abnormally) and cannot be reused. Ticket-level credential errors are identified by six-digit server codes (e.g., 401010 = ticket invalid/expired, 401011 = ticket already used), which the SDK passes through verbatim.

Environment Requirements

Item

Requirement

minSdk

24 (Android 7.0) and above

compileSdk

36

Language

Kotlin (coroutine suspend APIs)

ABI

arm64-v8a / armeabi-v7a (the real-time communication engine includes native libraries)

Network

Public internet access required

Core Concepts

Concept

Description

World

An AI world, containing characters and scenes. Created and managed by your server.

Travel

A single real-time experience. Basic lifecycle: init → pending → running → completed (failure is failed). In directing and Acting modes, if the experience supports pausing, running can enter paused via pauseTravel, then return to running via resumeTravel (directing can additionally use rewindTravel, after which the server resumes the experience automatically); running ⇄ paused can loop multiple times.

Mode

adventure (device/command interaction), directing (text-driven narrative), or acting (Acting: text-driven narrative whose playback aspect ratio is fixed by the server at world creation). The SDK uniformly exposes these three values.

Real-time video

Established and maintained automatically by the SDK after startTravel succeeds; you do not need to connect/disconnect manually. It is released automatically on endTravel.

Overview

HappyOyster methods

Method

Description

initialize(context, config)

Initializes the SDK. Idle re-initialization is supported; re-initialization while a Travel is starting, active, or ending is rejected with 103004.

updateToken(token)

Injects/updates the Bailian gateway API Key (Bearer token).

startTravel(ticket)

Starts an experience with a one-time credential and automatically establishes the real-time video connection.

pauseTravel()

Asynchronously pauses the experience (directing and Acting, and only if the experience supports pausing); after acceptance, the actual pause is determined by onStatusChanged(Paused).

resumeTravel()

Resumes a paused experience (directing and Acting); includes 3× retry backoff internally.

rewindTravel(rewindToSec)

Rewinds to the specified number of seconds (directing only, state paused). The seconds value must be a multiple of 4. Acting does not support rewind.

sendInstruct(content)

Sends a text instruction to drive the narrative (directing and Acting, running or paused).

sendCommand(command)

Sends direction/view/action control commands (adventure mode, running only; main thread). Not available for Acting.

attachVideo()

Returns a SurfaceView for playback; add it to your layout to render (main thread).

endTravel()

Ends the experience, automatically disconnecting the real-time connection and releasing all session resources.

VERSION

SDK version string (SemVer), a compile-time constant.

Events

Event

Description

onStatusChanged(status)

Experience status change (including the real-time video lifecycle); values are defined in TravelStatusValue.

onError(error)

Callback when an internal automatic flow fails; a fatal error also terminates the current experience.

Key data types

Type

Description

SDKConfig

SDK initialization configuration (apiHost and model are required, plus logLevel, logcatEnabled, logHandler, and callbackTimeoutMs).

TravelStatusValue

Experience status: Init / Pending / Running / Paused / Failed / Completed.

ModeValue

Experience mode: adventure / directing / acting.

CreationModelValue

World creation model: Simple (default, instruct-driven) / ScriptList (structured script, sendInstruct not supported).

StartTravelData

Returned by startTravel; contains metadata such as mode, version, creationModel, aspectRatio, and maxExperienceTimeSec.

AdventureCommand

Parameter for sendCommand; contains the three fields translation / rotation / interaction.

SDKError

SDK error; contains code: Int and optional raw: Any?.

HappyOyster.initialize

Initializes the SDK; call it before any other API (preferably in Application.onCreate). config is required and must carry your account's Bailian apiHost and a model of happyoyster-1.0-directing / happyoyster-1.0-acting / happyoyster-1.0-adventure (matching the Open API entry). The SDK constructs request URLs as follows:

https://{apiHost}/api/v2/apps/{model}/openapi/v1/{endpoint}

model has no default value: omitting it when constructing SDKConfig is a compile-time error. Passing a blank value makes initialize synchronously throw SDKError(100002); no runtime is created or replaced and no network request is made. The SDK holds only the application context, not an Activity. Re-initialization is supported only while the previous runtime is idle. To switch models, re-initialize with the new model while idle; if a Travel is starting, active, or ending, await endTravel() first.

Note

Regional compliance notice:
  • To support compliance with applicable legal and data requirements, if your intended users include U.S. users, you must configure a U.S.-region API Host during SDK initialization for services provided to those users.
  • Developers are responsible for correct configuration and bear the corresponding responsibility under applicable law for failure to follow this requirement.

Signature

fun initialize(context: Context, config: SDKConfig)

Parameters

Field

Type

Required

Description

context

Context

Yes

Passing applicationContext is recommended.

config

SDKConfig

Yes

SDK configuration; must carry the required, default-free apiHost and model.

Returns

No return value.

Errors

code

Description

100002

model is blank. Initialization fails synchronously with SDKError.raw set to SDKConfig.model must not be blank; no runtime is created or replaced and no network request is made.

103004

Re-initialization rejected because a Travel is starting, active, or ending. Await endTravel() and retry.

HappyOyster.updateToken

Injects/updates the Bailian gateway API Key. Thread-safe and callable after initialize; the SDK uses the latest token for startTravel and subsequent experience-control requests. The ticket is a one-time experience credential and should not be mixed up with the Bearer token.

Signature

fun updateToken(token: String)

Parameters

Field

Type

Required

Description

token

String

Yes

The Bailian gateway API Key, used as the Bearer token.

Returns

No return value.

Errors

code

Description

100001

SDK has not been initialized.

HappyOyster.startTravel

Starts an experience with a one-time ticket. On success, the SDK automatically establishes the real-time video connection and begins internal status polling, with status surfaced via onStatusChanged.

  • ticket is a one-time credential and is considered consumed once the call is made.
  • Only one concurrent Travel is allowed per app at any given moment; calling again while an experience is in progress throws SDKError(103004), with SDKError.raw containing only a redacted summary of the currently active ticket for diagnosis, never the full ticket.
  • Optionally pass maxExperienceTimeSec to cap the maximum duration of this experience (world-exploration / adventure mode only); see Parameters.

StartTravelData.creationModel (CreationModelValue, default CreationModelValue.Simple): indicates the world's creation model, i.e., how script content is managed. simple (default) is an ordinary instruct-driven world; world-exploration worlds are also normalized to this value. scriptlist is a structured ScriptList world—its script content is not accessed through this SDK. In ScriptList mode (creationModel == CreationModelValue.ScriptList), calling sendInstruct is rejected and throws SDKError(103002). This field defaults to simple.

StartTravelData.aspectRatio (String?): the playback aspect ratio the server assigned to this session, as a width:height string. Non-null only for Acting"9:16" (portrait, the server-side default at creation) or "16:9" (landscape); null for world-exploration and directing modes, and null when the server does not report the value. The SDK preserves unknown values verbatim (it does not collapse them to null), so hosts must treat any unrecognized value like null and fall back to their own default orientation; the SDK itself does not consume this field.

The value is delivered with the startTravel() return value: size the playback container from it after startTravel() returns and before calling attachVideo() and adding the returned view to your layout—the SDK only starts binding the remote stream for rendering once the host attaches the view, so deciding the orientation at that point still precedes the first rendered frame (it is, however, not guaranteed to happen before the SDK joins the real-time room). The remote view is bound with a clip-to-fill render mode, so a container whose orientation disagrees with this value crops the picture instead of letterboxing it.

Signature

suspend fun startTravel(ticket: String): StartTravelData
suspend fun startTravel(ticket: String, maxExperienceTimeSec: Int?): StartTravelData

Parameters

Field

Type

Required

Description

ticket

String

Yes

The one-time experience credential, delivered by your server.

maxExperienceTimeSec

Int?

No

Maximum duration of this experience in seconds; the world ends the session automatically once it is reached. Applies to world-exploration (adventure) mode only; ignored in directing (real-time directing) and Acting modes. The allowed values are server-configured, currently 60 / 90 / 120; passing null (or using the overload without this parameter) applies the server default (currently 60). Passing an unsupported value makes the server reject startTravel with 400000, throwing SDKError(400000) with no active Travel created. The SDK does not validate the value locally — the allowed set is authoritative on the server.

Returns

Returns StartTravelData, containing experience metadata (mode, version, creationModel, aspectRatio, etc.). Inspect the returned metadata before deciding how to interact (e.g., travel.mode, travel.creationModel, travel.version); for Acting, also size the playback container from travel.aspectRatio.

Errors

code

Description

401010

ticket invalid or expired

401011

ticket already used

400000

Invalid parameter (e.g. maxExperienceTimeSec not within the server-allowed set); this startTravel fails to start

403002

World not in a ready state

500001

Resource allocation / internal service failure

103004

A Travel is already starting, active, or ending, or startTravel is called concurrently (only one Travel is allowed at a time; SDKError.raw contains only a redacted summary of the active ticket)

HappyOyster.pauseTravel / HappyOyster.resumeTravel

Pause / resume the experience. The SDK manages only one Travel at a time; encryptedTravelId is obtained internally by the SDK and need not be passed by the caller.

Preconditions:

  • pauseTravel: callable only in directing or Acting and when the state is running.
  • resumeTravel: callable only in directing or Acting and when the state is paused.

Not all experiences support pause / resume: the experience must report the version identifier its mode requires (StartTravelData.versionstoryV2 for directing, actingV2 for Acting; compared ignoring surrounding whitespace and letter case), otherwise both pauseTravel and resumeTravel return 103002. Acting supports pause / resume but does not support rewindTravel.

Pause is asynchronous (important): pauseTravel is a "heavy" operation—a successful call (method return) only means the pause has been accepted; at that moment the experience is not actually paused yet. The pause is only real once the onStatusChanged callback reports paused. Therefore, drive the host state machine and call gating from that callback: between "calling pauseTravel" and "receiving the paused callback" you may mark the local state as pausing; only after receiving paused should you allow initiating resumeTravel or rewindTravel. Do not treat the return of pauseTravel as already paused.

Handling of the real-time connection: after an actual pause, the SDK disconnects the real-time connection; on resume, the SDK automatically rejoins and restores the picture using the credential delivered during the same startTravel, with no host intervention required.

Ordered pause-teardown barrier: after Paused is emitted, server-side real-time-room teardown may still lag briefly. The SDK establishes a 3-second settle window from pause confirmation. If resumeTravel or rewindTravel is called inside that window, the suspending call first waits non-blockingly for the remaining time, then sends the room-reopening API request. No delay is added if 3 seconds have already elapsed naturally. This prevents a late pause teardown from closing the newly opened room and surfacing 105001.

resume API retry: resumeTravel internally retries up to 3 times on failure (backoff 1 s / 2 s / 3 s) to handle brief service unavailability after a pause; only if all 3 attempts fail is the error propagated upward.

Note⚠️ Note: It is recommended that the host wait up to 3 s after resumeTravel returns successfully before initiating pauseTravel again, to avoid switching too frequently. This is a host-side call cooldown recommendation and is not enforced by the SDK itself. See Happy Oyster Android SDK Integration Guide for details.

Signature

suspend fun pauseTravel(): TravelStateData
suspend fun resumeTravel(): TravelStateData

Parameters

No parameters.

Returns

Returns TravelStateData (contains encryptedTravelId, status).

Errors

code

Description

103001

No active experience

103002

State not allowed, or this experience does not support pause / resume

103003

Called in adventure mode; only directing and Acting support pause / resume

HappyOyster.rewindTravel

Rewinds to the specified number of seconds. encryptedTravelId is obtained internally by the SDK.

Preconditions: callable only in directing mode and when the state is paused (rewinding is not allowed in the running state). Not all directing experiences support rewinding, and unsupported ones return 103002. After a successful rewind, the experience resumes automatically and the SDK automatically reconnects RTC, with no host intervention required.

Acting has no rewind capability at all: calling it in Acting is rejected locally by the SDK with 103003, and no request is issued. In Acting, hosts should hide the rewind entry point rather than merely disabling the button.

Signature

suspend fun rewindTravel(rewindToSec: Double): RewindTravelData

Parameters

Field

Type

Required

Description

rewindToSec

Double

Yes

The target number of seconds to rewind to, which should be a multiple of 4 (e.g., 4, 8, 12). A non-multiple of 4 is floored by the server down to the nearest smaller multiple of 4 (e.g., passing 7 yields 4).

Returns

Returns RewindTravelData (contains encryptedTravelId, status, resumedAtSec), where resumedAtSec is the number of seconds the server actually rewound to (already floored to a multiple of 4).

Errors

code

Description

103001

No active experience

103002

State is not paused, or this experience does not support rewinding

103003

Called in adventure or Acting mode; only directing mode supports rewinding

HappyOyster.sendInstruct

Sends a text instruction to drive the narrative. Valid in directing or Acting and when the experience state is running or paused. encryptedTravelId is obtained internally by the SDK.

Behavior in the paused state: the SDK does not automatically resume while paused; the instruct is sent directly. It is up to the host to decide whether to call resumeTravel first before sending the instruct.

Signature

suspend fun sendInstruct(content: String): SendInstructData

Parameters

Field

Type

Required

Description

content

String

Yes

The text instruction content to send.

Returns

Returns SendInstructData (contains encryptedTravelId, content, accepted).

Errors

code

Description

103001

No active experience (never called startTravel, or it has ended)

103003

Current mode is neither directing nor Acting (called in adventure mode)

103002

The experience state is neither running nor paused (e.g., still in the init/pending phase); or the current world is in ScriptList mode (StartTravelData.creationModel == CreationModelValue.ScriptList)—sending instruct is not allowed in ScriptList mode, where scripts are managed by the Bailian platform API

403004

Content moderation block

404000

Travel does not exist

HappyOyster.sendCommand

Sends direction/view/action control commands. Valid only in adventure mode and when running.

  • Call on the main thread; an off-main call synchronously throws IllegalStateException.
  • No active experience reports 103001.
  • Calling outside adventure mode (directing / Acting) reports 103003.
  • A disallowed state reports 103002.
  • The uplink is established via a silent audio stream (the SDK does not record or upload real audio); RECORD_AUDIO is not a hard prerequisite for the DataChannel, but declaring and granting it is recommended for cross-device compatibility (see Happy Oyster Android SDK Integration Guide § Installation · Permissions). 105004 means the real-time channel is not ready / a send failed (e.g. a DataChannel connection interruption or a real-time channel anomaly); it is not triggered directly by a missing permission.

Built-in 42 ms throttling (24 fps, latest-wins): sendCommand internally throttles DataChannel writes in 42 ms intervals (about 24 fps). State validation runs synchronously and immediately, throwing immediately on an illegal call; the actual DataChannel write is throttled asynchronously. If at least 42 ms has passed since the previous send, the command is dispatched immediately (including the first call). Otherwise, the pending command is replaced with the latest value and dispatched once at the end of the current 42 ms window. Multiple calls in one window therefore produce one on-wire write containing the last value. The host may call sendCommand at the game frame rate without implementing its own rate limiter.

Send failures during throttling: the throttled flush is asynchronous, and a send failure cannot be thrown to the caller—the error is surfaced via the onError callback (non-fatal, 105004). Pending commands are discarded when the session ends (they will not be sent after the session ends).

AdventureCommand fields and values:

Field

Semantics

Values

translation

Movement: forward/left/back/right/diagonal/idle

W / A / S / D / W_A / W_D / S_A / S_D / None

rotation

View: up/down/left/right/diagonal/none

Mouse_Up / Mouse_Down / Mouse_Left / Mouse_Right / Mouse_Up_Left / Mouse_Up_Right / Mouse_Down_Left / Mouse_Down_Right / None

interaction

Interaction: jump/attack/squat/sprint/none

Jump / Attack / Squat / Sprint / None

The three fields are independent mutually exclusive command groups. Diagonal movement/view uses a single combined value (for example, forward+left is W_A, not concurrent W and A in the same field). Each call should carry the complete current state.

Best practices: one-shot actions vs held actions

Use the 42 ms interval as the mental model:

  • One-shot action (for example, tap jump/attack or take one step): call once. The SDK sends it in the nearest interval; no repeated calls or follow-up None command is required.

    // Jump once
    HappyOyster.sendCommand(AdventureCommand("None", "None", "Jump"))
    
  • Held action (for example, keep moving or rotating): call every frame while held. The SDK emits approximately one command every 42 ms. On release, explicitly send one command containing None to reset the state. The SDK never generates a reset command automatically.

    // While held: call from the host's frame loop
    HappyOyster.sendCommand(AdventureCommand("W", "None", "None"))
    // On release: explicitly reset once
    HappyOyster.sendCommand(AdventureCommand("None", "None", "None"))
    

Signature

fun sendCommand(command: AdventureCommand)

Parameters

Field

Type

Required

Description

command

AdventureCommand

Yes

A command object containing the three fields translation, rotation, interaction.

Returns

No return value (synchronous). State validation runs synchronously and immediately; the DataChannel write is throttled asynchronously.

Errors

code

Description

103001

No active experience

103002

State not allowed

103003

Called outside adventure mode (directing / Acting)

105004 (via onError)

Throttled flush send failure (non-fatal, asynchronous callback)

HappyOyster.attachVideo

Returns a SurfaceView for playback, which you add to your layout; the SDK internally completes the render binding with the remote stream.

  • Call on the main thread; an off-main call synchronously throws IllegalStateException.
  • The SDK holds only a weak reference to the returned View and releases the render binding when the experience ends; you must remove the View from the layout yourself.
  • For Acting experiences, size the playback container from StartTravelData.aspectRatio before adding the returned view to your layout: rendering is bound with a clip-to-fill mode, so a mismatched orientation crops the picture (see startTravel).

Signature

fun attachVideo(): SurfaceView

Parameters

No parameters.

Returns

Returns a SurfaceView; add it to your layout to play the real-time video.

Errors

code

Description

100001

SDK not initialized

HappyOyster.endTravel

Ends the experience. encryptedTravelId is obtained internally by the SDK. After a successful call (or an abnormal exit), the SDK automatically disconnects the real-time connection, stops internal polling, and releases all session resources, and the current ticket is invalidated at the same time.

Signature

suspend fun endTravel(): EndTravelData

Parameters

No parameters.

Returns

Returns EndTravelData (contains encryptedTravelId, status, endedAt, durationSec).

Errors

code

Description

100001

SDK not initialized

103001

No active experience

HappyOyster.VERSION

Returns the SDK version string (SemVer), such as "x.y.z". This value is a compile-time constant injected by the VERSION_NAME Gradle property; it can be read safely without calling initialize first.

Signature

val VERSION: String

Returns

The SDK version string, such as "x.y.z".

Log.d("MyApp", "SDK version: ${HappyOyster.VERSION}")

Event Listening

interface HappyOysterListener {
    fun onStatusChanged(status: TravelStatusValue) {}
    fun onError(error: SDKError) {}
}

fun addListener(listener: HappyOysterListener)
fun removeListener(listener: HappyOysterListener)

SDK events are a proactive push channel to you, used to report situations not triggered by your own explicit calls (e.g., problems in the real-time connection or status polling that the SDK maintains automatically).

Event

Description

onStatusChanged

Experience status change (including the real-time video lifecycle); values are defined in TravelStatusValue.

onError

Callback when an internal automatic flow fails; a fatal error also terminates the current experience (see the error-code section).

Note⚠️ Note: addListener / removeListener must be called after initialize; calling them before initialization throws SDKError(100001). It is recommended to register listeners immediately after HappyOyster.initialize(...) returns successfully.

Data Models

// Configuration
data class SDKConfig(
    // Required: your account's Bailian API Host (e.g., llm-xxxx.ap-southeast-1.maas.aliyuncs.com), copied from the API Key page in the Bailian console;
    // must belong to the same account/region as the injected API Key, otherwise the gateway returns AccessDenied.
    val apiHost: String,
    // Required with no default: the complete HappyOyster model name and version (e.g., happyoyster-1.0);
    // refer to the official Bailian HappyOyster model documentation for available values.
    val model: String,
    // Minimum level for the built-in Logcat sink (does not affect logHandler); defaults to INFO, which
    // includes the lifecycle anchors (initialize, Travel start/status/end, RTC connect/first-frame) needed
    // to reconstruct a session timeline. Drop to WARN for errors-only, or raise to DEBUG/VERBOSE to diagnose.
    val logLevel: LogLevel = LogLevel.INFO,
    // Timeout for RTC join (timeout triggers 105002), first-frame wait (timeout triggers 105003), and SDK gateway HTTP signaling calls (timeout triggers 105005);
    // the two RTC phases are serial, so the worst-case wait is 2×callbackTimeoutMs (60s).
    val callbackTimeoutMs: Long = SDKConfig.DEFAULT_CALLBACK_TIMEOUT_MS, // 30_000ms
    // Whether the SDK writes its own logs to Android Logcat (tag HappyOysterSDK); defaults to false (silent).
    val logcatEnabled: Boolean = false,
    // Host log callback; receives the full stream of SDK LogRecords, unaffected by logLevel; defaults to null.
    val logHandler: HappyOysterLogHandler? = null,
) {
    companion object {
        /** Default callback timeout (milliseconds). Public constant, usable for comparison or display. */
        const val DEFAULT_CALLBACK_TIMEOUT_MS: Long = 30_000
    }
}

enum class LogLevel { VERBOSE, DEBUG, INFO, WARN, ERROR, NONE }

// Host log sink; injected via SDKConfig.logHandler; full firehose, unaffected by logLevel.
fun interface HappyOysterLogHandler {
    fun onLog(record: LogRecord)
}

// SDK structured log record; delivered to HappyOysterLogHandler; contains no sensitive values (Bearer /
// ticket / RTC token, RTC identity fields, and media URLs are all redacted). travelId (encryptedTravelId)
// is kept in full so it can be correlated with server-side session logs.
data class LogRecord(
    val level: LogLevel,
    val tag: String,        // fixed as "HappyOysterSDK"
    val message: String,    // readable log line (includes event name and details)
    val throwable: Throwable?,
    val timestampMs: Long,  // epoch milliseconds at emission time
)

// Status and mode (unknown values are preserved to allow service extension)
@JvmInline value class TravelStatusValue(val rawValue: String) {
    companion object {
        val Init = TravelStatusValue("init")
        val Pending = TravelStatusValue("pending")
        val Running = TravelStatusValue("running")
        val Paused = TravelStatusValue("paused")
        val Failed = TravelStatusValue("failed")
        val Completed = TravelStatusValue("completed")
    }
}
@JvmInline value class ModeValue(val rawValue: String) {
    companion object {
        val Adventure = ModeValue("adventure")
        val Directing = ModeValue("directing")
        val Acting = ModeValue("acting")
    }
}
@JvmInline value class CreationModelValue(val rawValue: String) {
    companion object {
        val Simple = CreationModelValue("simple")         // default; prompt/instruct-driven, world-exploration worlds normalize to this
        val ScriptList = CreationModelValue("scriptlist") // structured ScriptList world; sendInstruct not supported (throws 103002)
    }
}

// startTravel return
data class StartTravelData(
    val encryptedTravelId: String, // identifier for subsequent control interfaces
    val encryptedWorldId: String,
    val mode: ModeValue,           // adventure / directing / acting
    val creationModel: CreationModelValue = CreationModelValue.Simple, // world creation model; Simple (default) is instruct-driven, ScriptList does not support sendInstruct
    val playUrl: String?,
    val firstFrame: String?,       // first-frame image address, may be produced asynchronously
    val bgmUrl: String?,
    val version: String,           // world version identifier; Acting is actingV2
    val aspectRatio: String? = null, // Acting only: 9:16 / 16:9; other modes null. Use to set player orientation
    val maxExperienceTimeSec: Int? = null, // server-reported max experience time (seconds); non-null only for adventure; null for directing / acting
)

// Control interface parameters and returns
data class AdventureCommand(
    val translation: String, // movement: forward/left/back/right/diagonal (W_A, …)/idle
    val rotation: String,    // view: up/down/left/right/diagonal (Mouse_Up_Left, …)/none
    val interaction: String, // interaction: jump/attack/squat/sprint/none
)
data class TravelStateData(val encryptedTravelId: String, val status: TravelStatusValue)
data class RewindTravelData(val encryptedTravelId: String, val status: TravelStatusValue, val resumedAtSec: Double)
data class EndTravelData(val encryptedTravelId: String, val status: TravelStatusValue, val endedAt: String, val durationSec: Int)
data class SendInstructData(val encryptedTravelId: String, val content: String, val accepted: Boolean)

// Error (identified by code; the original information is in raw)
// Note: SDKError is not a data class (no copy()/destructuring), it is a plain class.
class SDKError(val code: Int, val raw: Any? = null) : Exception("Happy Oyster SDK error: $code")

Error Codes

Errors are identified by a numeric code. The SDK passes through business error codes that the caller can handle (commonly 4xxxxx / 5xxxxx); local SDK error codes are 1xxxxx.

Business error codes (common)

code

Meaning

Suggested handling

400000

Invalid parameter (invalid enum, etc.)

Check the request parameters or the SDK version

401010

ticket invalid or expired

Have your server re-issue a credential

401011

ticket already used

The credential is one-time; re-issue it

403001

World does not exist, has been deleted, or does not belong to the current developer (including a world already deleted within the startTravel credential)

Reselect a valid World

403002

World not in a ready state

Wait until the world is ready before starting

403004

Input content violation (content moderation); applies to sendInstruct text instructions

Modify the input content and retry

403007

Service specification not enabled for this account

Do not retry as capacity-full; switch to an enabled spec or request enablement

403008

Capacity configuration temporarily unavailable

Retry later

404000

Travel resource does not exist or the ID does not belong to the current account

Restart the Travel

409000

Request conflicts with the current resource state

Check the Travel state

429001

SKU concurrency limit reached

Retry after an existing session ends (do not confuse with 500001)

429002

No capacity currently available

Retry later

500001

Inference resource allocation / internal service failure

Retry later

500000

Internal system error

Retry later / report

Client-side local error codes

code

Meaning

Fatal?

Suggested handling

100001

Called before the SDK was initialized

Call rejected

initialize first

100002

model was blank during SDK initialization; thrown synchronously with SDKError.raw set to SDKConfig.model must not be blank; no runtime is created or replaced and no network request is made

Initialization failed

Pass a non-blank complete model name and version, then call initialize again; refer to the official Bailian HappyOyster model documentation for available values

101001

Bailian gateway API Key not injected

No

Retry after updateToken

101002

Bailian gateway Bearer API Key expired or rejected. Note: this is the gateway Bearer Key, not the one-time ticket; ticket-level credential errors are identified by six-digit server codes (e.g., 401010/401011)

No

Re-obtain the Bearer Key and updateToken; no need to exchange for a new ticket

103001

No active experience currently

Call rejected

startTravel first

103002

The current state does not allow this operation (including: state mismatch; the experience does not support pause / resume / rewind for pauseTravel/resumeTravel/rewindTravel (its version is not the v2 identifier its mode requires); state not paused for rewindTravel; creationModel == ScriptList for sendInstruct)

Call rejected

Check the experience state and mode; sendInstruct is unavailable in ScriptList mode (creationModel == CreationModelValue.ScriptList), where script content is not managed through this SDK

103003

Mode mismatch: sendCommand called in directing or Acting, or pauseTravel / resumeTravel / rewindTravel / sendInstruct called in adventure, or rewindTravel called in Acting

Call rejected

Check whether the current mode matches the interface's requirement

103004

A concurrent startTravel call, or re-initialization while a Travel is starting, active, or ending (only one Travel is allowed at a time; SDKError.raw for concurrent start contains only a redacted ticket summary)

Call rejected

Await endTravel() before retrying

105001

Real-time connection failed

Yes

End and restart

105002

Real-time join timed out

Yes

End and restart

105003

Timed out waiting for the video first frame

Yes

End and restart

105004

Real-time channel not ready / send failed

No

Confirm the real-time channel is ready and retry sendCommand after running; if a specific device misbehaves, try declaring and granting RECORD_AUDIO (see Happy Oyster Android SDK Integration Guide § Installation · Permissions)

105005

An SDK gateway HTTP signaling call did not return within callbackTimeoutMs (default 30s); RTC join and first-frame timeouts use 105002 and 105003, respectively

No

Retrying is recommended, or increase callbackTimeoutMs

105006

Auto-end on no stream: the first frame never arrived, or the stream was interrupted during running and did not recover before timeout. The SDK proactively ends the current experience

Yes

End and restart

106001

Local network error

No

Retryable

106002

Response parsing failed

No

Thrown to the caller when triggered by an explicit call; on internal status polling, only onError is emitted and the experience is not terminated

106003

The upstream service returned an unrecognizable error response; the SDK has kept the original information in SDKError.raw (including gateway edge rejections such as AccessDenied)

No

Retryable; if it persists, investigate service availability together with raw. If raw contains AccessDenied (most often on the first request after initialization), this is a gateway configuration issue; check in order: ① whether the model name and version are correct, still available, published, and authorized; ② apiHost spelling; ③ whether apiHost, model, and the Key injected via updateToken match the required account and region; ④ whether your account has been added to the app allowlist

108001

SDK remotely disabled (fully shut down or version too low); the reason is in SDKError.raw (String)

Yes (when the SDK detects a disable result, it automatically ends the in-progress experience; you can restart after recovery)

Prompt the user based on the reason in raw; guide an upgrade when the version is too low

Note"Fatal?" refers specifically to whether it triggers the SDK to automatically terminate the current experience. Synchronous validation errors such as 100001/100002/103xxx only reject/throw for that call (100002 directly fails initialization) and do not terminate an experience.

Fatal vs non-fatal:

  • Fatal errors: the SDK automatically terminates the current experience (disconnects the real-time connection, releases resources, calls endTravel) and surfaces it via onError; the host should clean up the current experience state and allow a restart. There are four categories: ① internal status polling reads an experience status of failed (e.g., 500001 inference failure); ② real-time connection fatal (105001/105002/105003); ③ auto-end on no stream (105006: the first frame never arrived, or the stream was interrupted during running and did not recover before timeout, and the SDK proactively ends it); ④ SDK remotely disabled (108001).
  • Non-fatal errors: do not terminate the experience and are only surfaced via onError; you may updateToken again or wait for the service / real-time channel to recover before continuing. A single failed request of internal status polling itself (network 106001, parsing 106002, upstream anomaly 106003, business error 5xxxxx) falls into this category—polling continues in the next cycle, and only when it reads a status of failed does it end the experience; authentication failure 101001 and an asynchronous sendCommand throttled-flush send failure 105004 are likewise non-fatal. Note: the SDK never sends any keepalive payload of its own over the real-time channel, so 105004 cannot appear while the session is idle—it can only be triggered by your own sendCommand call.