> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mobilerun.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Network inspection

> Stream decoded HTTP and WebSocket traffic from a supported Mobilerun device.

Mobilerun can inspect a device's network traffic and stream decoded HTTP/1.1, HTTP/2, HTTP/3, and application WebSocket events to your local machine. The stream is live-only and does not require adb or direct access to the device.

Unlike the [ADB and Frida tunnels](/integrations/adb), network inspection is session-based. You start a session with the REST API, connect to the returned WebSocket with a short-lived viewer token, and stop the session when you are finished.

## Requirements

<Note>
  Network inspection must be enabled for the device's hosting pool. Contact Mobilerun support at [contact@mobilerun.ai](mailto:contact@mobilerun.ai) if you need access.
</Note>

* A Mobilerun [API key](/api-keys) (`dr_sk_...`)
* [`curl`](https://curl.se), [`jq`](https://jqlang.org), and [`websocat`](https://github.com/vi/websocat) installed locally

Set the API key and device ID in your shell:

```bash theme={null}
export MOBILERUN_API_KEY='dr_sk_...'
export DEVICE_ID='YOUR_DEVICE_ID'
export MOBILERUN_API='https://api.mobilerun.ai/v1'
```

Check whether the device supports network inspection:

```bash theme={null}
curl --fail-with-body -sS \
  -H "Authorization: Bearer $MOBILERUN_API_KEY" \
  "$MOBILERUN_API/devices/$DEVICE_ID/capabilities" \
  | jq '.capabilities.trafficInspection'
```

The value must be `true`. The capability is granted when a device is placed in an enabled pool. Disabling inspection for that pool also prevents new sessions and stops active sessions.

## Endpoints

| Method   | Endpoint                                              | Purpose                                         |
| -------- | ----------------------------------------------------- | ----------------------------------------------- |
| `POST`   | `/v1/devices/{deviceId}/traffic/sessions`             | Start a session and receive stream credentials. |
| `GET`    | `/v1/devices/{deviceId}/traffic/sessions`             | List recent session metadata.                   |
| `GET`    | `/v1/devices/{deviceId}/traffic/sessions/{sessionId}` | Read status and issue fresh stream credentials. |
| `DELETE` | `/v1/devices/{deviceId}/traffic/sessions/{sessionId}` | Stop a session and restore device networking.   |
| `WSS`    | Returned as `stream.url`                              | Receive live decoded traffic events.            |

Use your Mobilerun API key for the REST endpoints. The WebSocket uses the session-scoped viewer token returned in `stream.token`; always connect to the returned `stream.url` instead of constructing it yourself.

## Start an inspection session

Create a session with a unique `Idempotency-Key` so retrying the same request does not start another session:

```bash theme={null}
export REQUEST_ID="traffic-$(date +%s)-$$"

SESSION="$(
  curl --fail-with-body -sS -X POST \
    -H "Authorization: Bearer $MOBILERUN_API_KEY" \
    -H "Idempotency-Key: $REQUEST_ID" \
    -H 'Content-Type: application/json' \
    -d '{"expiresInSeconds":3600,"maxBodyBytes":1048576}' \
    "$MOBILERUN_API/devices/$DEVICE_ID/traffic/sessions"
)"

export SESSION_ID="$(jq -er '.id' <<<"$SESSION")"
jq '{id, state, expiresAt, maxBodyBytes, retention}' <<<"$SESSION"
```

`expiresInSeconds` can be 60–14400 seconds and defaults to 3600. `maxBodyBytes` can be 0–10485760 bytes and defaults to 1048576. Set it to `0` to receive request and response metadata without body content.

Only one session can be `starting`, `active`, or `stopping` for a device at a time. Starting another returns `409 TRAFFIC_ALREADY_ACTIVE`.

## Wait until the session is active

Starting inspection is asynchronous. Poll the status endpoint until the device producer is ready:

```bash theme={null}
while true; do
  STATUS="$(
    curl --fail-with-body -sS \
      -H "Authorization: Bearer $MOBILERUN_API_KEY" \
      "$MOBILERUN_API/devices/$DEVICE_ID/traffic/sessions/$SESSION_ID"
  )"

  STATE="$(jq -er '.state' <<<"$STATUS")"
  [ "$STATE" = 'active' ] && break

  if [ "$STATE" != 'starting' ]; then
    jq . <<<"$STATUS"
    exit 1
  fi

  sleep 1
done

export STREAM_URL="$(jq -er '.stream.url' <<<"$STATUS")"
export VIEWER_TOKEN="$(jq -er '.stream.token' <<<"$STATUS")"
export STREAM_PROTOCOL="$(jq -er '.stream.protocol' <<<"$STATUS")"
```

The status response issues a fresh, short-lived viewer token while the session is `starting` or `active`. The token is restricted to this owner, device, and session. Use it only for the returned stream URL; do not substitute your Mobilerun API key.

## Stream events with websocat

Open the returned WebSocket URL with the viewer token and required subprotocol:

```bash theme={null}
websocat \
  -H="Authorization: Bearer $VIEWER_TOKEN" \
  -H="Sec-WebSocket-Protocol: $STREAM_PROTOCOL" \
  "$STREAM_URL" \
  | jq --unbuffered -c .
```

Exercise the app on the device while this command is running. Each line is a JSON event. The first event is `hello`; subsequent event types are:

| Type                | Meaning                                                          |
| ------------------- | ---------------------------------------------------------------- |
| `ready`             | The on-device traffic producer connected.                        |
| `flow`              | A decoded HTTP exchange or an interception failure.              |
| `websocket-message` | An application WebSocket message associated with a flow.         |
| `gap`               | Events were dropped because the bounded device queue overflowed. |

Request and response bodies appear as `request_body_base64` and `response_body_base64`. WebSocket payloads appear as `payload_base64`. Headers remain arrays of name-value pairs so repeated headers are preserved.

For example, decode response bodies with `jq`:

```bash theme={null}
websocat \
  -H="Authorization: Bearer $VIEWER_TOKEN" \
  -H="Sec-WebSocket-Protocol: $STREAM_PROTOCOL" \
  "$STREAM_URL" \
  | jq --unbuffered -r \
      'select(.type == "flow" and .flow.response_body_base64) | .flow.response_body_base64 | @base64d'
```

<Warning>
  Inspected headers and bodies can contain credentials, personal data, and other secrets. Mobilerun does not retain session events, but anything you print, pipe, or redirect locally may be stored on your machine.
</Warning>

## Stop the session

Stop inspection when you are finished so the device can restore its normal routing and temporary trust changes:

```bash theme={null}
curl --fail-with-body -sS -X DELETE \
  -H "Authorization: Bearer $MOBILERUN_API_KEY" \
  "$MOBILERUN_API/devices/$DEVICE_ID/traffic/sessions/$SESSION_ID" \
  | jq '{id, state}'
```

The request returns `202 Accepted` while cleanup runs. Repeating it is safe. Sessions also stop automatically when they expire.

## Troubleshooting

* **`TRAFFIC_NOT_SUPPORTED`** — The device image does not support network inspection.
* **`TRAFFIC_NOT_ENTITLED`** — `capabilities.trafficInspection` is `false`; create a device in an enabled pool or contact support.
* **`TRAFFIC_ALREADY_ACTIVE`** — Another session is starting, active, or stopping. Use the session ID in `details.sessionId`, or list sessions with `GET /devices/{deviceId}/traffic/sessions`.
* **`TRAFFIC_DEVICE_NOT_READY`** — Wait until the device reaches the `ready` state and retry with the same idempotency key.
* **`401 Unauthorized` on the WebSocket** — Use a fresh `.stream.token` from the session status response, not the Mobilerun API key.
* **WebSocket subprotocol error** — Send `Sec-WebSocket-Protocol: mobilerun.traffic.v1` in addition to the viewer token.
* **The viewer token expired or the socket closed with `1013`** — Read the session status again to mint a fresh token, then reconnect. Missed events are not replayed.
