Snap-O

Network Inspector Guide

Capture network requests from an Android app with Snap-O, including response bodies, Server-Sent Events, and WebSocket messages. Works with OkHttp, Ktor's OkHttp engine, and HttpURLConnection.

Use Maven Central

Snap-O publishes Android libraries version 3.1.1 and newer to Maven Central. Most Android projects already include mavenCentral(); add it to your dependency sources if yours does not.

settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

Add the Android dependency

Choose the dependency that matches your network client. Use the real interceptor in debug builds and its no-op counterpart in release builds. The no-op artifact preserves the same API while passing traffic through without starting the Snap-O server.

OkHttp or Ktor

Use the OkHttp interceptor for OkHttp directly or through Ktor's OkHttp engine.

gradle/libs.versions.toml
[versions]
snapo = "3.1.1"

[libraries]
snapo-network-okhttp3 = { module = "com.openai.snapo:network-okhttp3", version.ref = "snapo" }
snapo-network-okhttp3-noop = { module = "com.openai.snapo:network-okhttp3-noop", version.ref = "snapo" }
app/build.gradle.kts
dependencies {
    debugImplementation(libs.snapo.network.okhttp3)
    releaseImplementation(libs.snapo.network.okhttp3.noop)
}
HttpURLConnection

Use the HttpURLConnection interceptor on Android 7.0 (API 24) or newer.

gradle/libs.versions.toml
[versions]
snapo = "3.1.1"

[libraries]
snapo-network-httpurlconnection = { module = "com.openai.snapo:network-httpurlconnection", version.ref = "snapo" }
snapo-network-httpurlconnection-noop = { module = "com.openai.snapo:network-httpurlconnection-noop", version.ref = "snapo" }
app/build.gradle.kts
dependencies {
    debugImplementation(libs.snapo.network.httpurlconnection)
    releaseImplementation(libs.snapo.network.httpurlconnection.noop)
}

Add request interceptors

Add the interceptor once when the client is created. Requests made by that client can then be captured and made available to Snap-O. WebSockets require the wrapped factory shown below.

OkHttp

Kotlin
import com.openai.snapo.network.okhttp3.SnapOOkHttpInterceptor

val client = OkHttpClient.Builder()
    .addInterceptor(SnapOOkHttpInterceptor())
    .build()
Requests are buffered on the device for up to five minutes by default, so Snap-O can open after the app starts and still show recent traffic.
Ktor with the OkHttp engine

Attach the same interceptor through Ktor's OkHttp engine.

Kotlin
import com.openai.snapo.network.okhttp3.SnapOOkHttpInterceptor

val client = HttpClient(OkHttp) {
    engine {
        addInterceptor(SnapOOkHttpInterceptor())
    }
}

If your app already builds an OkHttp client, pass that client to Ktor instead.

Kotlin · preconfigured client
val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(SnapOOkHttpInterceptor())
    .build()

val client = HttpClient(OkHttp) {
    engine {
        preconfigured = okHttpClient
    }
}
HttpURLConnection

Open the connection through the Snap-O interceptor. Response details are captured when your app reads the response code or response stream.

Kotlin
import com.openai.snapo.network.httpurlconnection.SnapOHttpUrlInterceptor

val interceptor = SnapOHttpUrlInterceptor()
val connection = interceptor.open(URL("https://example.com"))

connection.connect()
connection.inputStream.use { body ->
    // Read the response.
}
connection.disconnect()

You can also wrap a connection created elsewhere.

Kotlin · existing connection
val existing = URL("https://example.com")
    .openConnection() as HttpURLConnection
val connection = SnapOHttpUrlInterceptor().intercept(existing)
WebSockets

Create WebSockets through the wrapped factory. Regular HTTP calls can continue using the original client.

Kotlin · OkHttp
import com.openai.snapo.network.okhttp3.withSnapOInterceptor

val webSocketFactory = client.withSnapOInterceptor()
val webSocket = webSocketFactory.newWebSocket(request, listener)

For Ktor, use the same preconfigured OkHttp client for HTTP traffic, wrap its WebSocket factory, and install Ktor's WebSockets plugin.

Kotlin · Ktor WebSockets
val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(SnapOOkHttpInterceptor())
    .build()

val client = HttpClient(OkHttp) {
    engine {
        preconfigured = okHttpClient
        webSocketFactory = okHttpClient.withSnapOInterceptor()
    }
    install(WebSockets)
}

Verify the connection

  1. Install and launch the debug build on an authorized Android device or emulator.
  2. Open Snap-O on macOS and select the connected device.
  3. Open Tools → Network Inspector, use the toolbar network icon, or press ⌘⌥I.
  4. Select the app process if prompted, then trigger a request in the Android app.
  5. Open the request to inspect headers, bodies, timing, SSE, or WebSocket messages.

If the Android app restarts, Snap-O may offer a newer process in the inspector sidebar. Switch to it to continue with live traffic.

Troubleshooting

  • Confirm the device is online and authorized in adb devices.
  • Confirm the installed variant includes the debug interceptor, not the no-op release artifact.
  • Make sure the request uses the exact OkHttp client, Ktor engine, or HttpURLConnection wrapper you configured.
  • Keep the Android app process running and select the newest process after an app restart.
  • Update the Snap-O desktop app and Android dependency together if a protocol mismatch appears.
  • The replay buffer retains up to five minutes, 10,000 events, or 16 MiB of total event data, whichever limit is reached first.
  • Individual request and response bodies are captured up to 5 MiB by default; larger bodies are truncated.

Advanced setup

Debug builds initialize the on-device server automatically through SnapONetworkInitProvider. Most apps should keep the defaults.

Provider configuration

The provider supports automatic initialization, main-process filtering, a mode label, and replay limits. Manifest overrides must use Android manifest-merger directives because the library already declares these metadata entries.

AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application>
        <provider
            android:name="com.openai.snapo.network.SnapONetworkInitProvider"
            android:authorities="${applicationId}.snapo-network-init"
            android:exported="false">
            <meta-data
                android:name="snapo.auto_init"
                android:value="false"
                tools:replace="android:value" />
        </provider>
    </application>
</manifest>
Metadata key Default Purpose
snapo.auto_init true Starts the inspector during app initialization.
snapo.main_process_only true Restricts automatic initialization to the app's main process.
snapo.mode_label safe Reports a custom mode label to Snap-O clients.
snapo.allow_release false Allows the server in a non-debuggable build. Enable only intentionally.
snapo.buffer_window_ms 300000 Sets the rolling replay window in milliseconds.
snapo.max_events 10000 Caps the number of events retained for replay.
snapo.max_bytes 16777216 Caps total retained event data at 16 MiB.
Manual initialization

If automatic initialization is disabled, initialize the inspector from your application process with a custom NetworkInspectorConfig.

Kotlin
import com.openai.snapo.network.NetworkInspector
import com.openai.snapo.network.NetworkInspectorConfig
import kotlin.time.Duration.Companion.minutes

NetworkInspector.initialize(
    application,
    NetworkInspectorConfig(
        bufferWindow = 10.minutes,
        maxBufferedEvents = 20_000,
        maxBufferedBytes = 32L * 1024 * 1024,
        modeLabel = "debug",
    ),
)