Snap-O

Network 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.

To edit API responses or return mock data with Python handlers, see Network Interception.

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 = "13.1.0"

[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 = "13.1.0"

[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 → Show Tool Pane, use the toolbar tool button, or press ⌘⌥I.
  4. Find the app process in the picker and click its Network icon, then trigger a request in the Android app.
  5. Open the request to inspect headers, bodies, timing, SSE, or WebSocket messages.

Each running app process has one picker row, with shortcuts for its available tools. At startup, Snap-O restores your last app and tool when available, or selects another available app. During a session, it keeps captured requests visible through disconnects and reconnects when the selected app returns.

If the selected Android app stops, click Open app on the waiting screen when available, or launch it on your device.

Filter traffic

Right-click a request to add its host to the exclusion filter. Exclusion filters are saved across sessions. Use the settings button beside the exclusion summary to add or remove filters. You can also search requests without changing your saved filters.

Inspect responses

Use the arrow keys to move between requests. Server-Sent Events show whether the stream is pending, streaming, closed, or offline. When a response body is unavailable, Snap-O explains whether it is no longer retained or is not cached on this Mac. If loading fails while connected, click Retry.

The request list follows new calls at the bottom, or at the top when sorted newest-first. Scroll away from that edge to pause, then return to resume. While paused, the list keeps the same request in view as calls arrive or sorting changes. Your selected request stays selected.

Intercept requests

Use snapo-network intercept to change a real API response or return mock data. The tool shows the response delivered to the app while your Python handlers run separately. Follow the Network Interception guide for requirements, examples, and supported traffic.

Command line

Use snapo-network to read requests and responses from your terminal. It works on macOS and Linux without the Mac app running.

Install the command-line tool

You'll need Python 3, Android Platform Tools, and Network enabled in your Android app.

On macOS, the tool comes with Snap-O. Run this to make it available in your terminal:

bash
export PATH="/Applications/Snap-O.app/Contents/MacOS:$PATH"

On Linux, or on macOS without Snap-O, download it directly:

bash
mkdir -p ~/.local/bin
curl -fsSL https://raw.githubusercontent.com/openai/snap-o/main/skills/snap-o-network-inspector/scripts/snapo-network -o ~/.local/bin/snapo-network
chmod +x ~/.local/bin/snapo-network
export PATH="$HOME/.local/bin:$PATH"

Add the export line to your shell settings to keep it for future terminal sessions.

Find your app, list its requests, then open one:

bash
snapo-network list --json
snapo-network requests -s SERIAL -n SOCKET --no-stream --json
snapo-network show -s SERIAL -n SOCKET -r REQUEST_ID --json

Use the deviceId and socketName from the first command for SERIAL and SOCKET. Replace REQUEST_ID with an ID from the request list.

Leave out --no-stream to watch new requests as they happen. To change or mock responses, see Network Interception. Run snapo-network --help for more commands.

Codex skill

The Network Inspector skill lives in skills/snap-o-network-inspector. It includes the CLI and instructions for inspecting traffic and changing or mocking responses.

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. Release builds do not start the server unless Network is explicitly enabled. Most apps should keep the defaults and use the no-op release artifact.

Enable Network in release builds

If you intentionally include the real network dependency in a release build, add the following metadata directly to your application's <application> element. This opt-in applies only to Network. No-op release artifacts remain the recommended setup for most apps.

AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <meta-data
            android:name="snapo.network.allow_release"
            android:value="true" />
    </application>
</manifest>
Provider configuration

The provider supports automatic initialization, main-process filtering, 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 tool during app initialization.
snapo.main_process_only true Restricts automatic initialization to the app's main process.
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 tool 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,
    ),
)