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.
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.
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
import com.openai.snapo.network.okhttp3.SnapOOkHttpInterceptor
val client = OkHttpClient.Builder()
.addInterceptor(SnapOOkHttpInterceptor())
.build()Ktor with the OkHttp engine
Attach the same interceptor through Ktor's OkHttp engine.
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.
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.
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.
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.
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.
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(SnapOOkHttpInterceptor())
.build()
val client = HttpClient(OkHttp) {
engine {
preconfigured = okHttpClient
webSocketFactory = okHttpClient.withSnapOInterceptor()
}
install(WebSockets)
}Verify the connection
- Install and launch the debug build on an authorized Android device or emulator.
- Open Snap-O on macOS and select the connected device.
- Open Tools → Show Tool Pane, use the toolbar tool button, or press ⌘⌥I.
- Find the app process in the picker and click its Network icon, then trigger a request in the Android app.
- 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:
export PATH="/Applications/Snap-O.app/Contents/MacOS:$PATH"On Linux, or on macOS without Snap-O, download it directly:
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:
snapo-network list --json
snapo-network requests -s SERIAL -n SOCKET --no-stream --json
snapo-network show -s SERIAL -n SOCKET -r REQUEST_ID --jsonUse 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.
<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.
<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.
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,
),
)