Self-hosted · Android · Kotlin & Java
See inside your Android app, from your desk.
Every network request, the screen, the databases and the preferences of any install — a tester's phone across the office or a customer's across the country — live in one dashboard on your own server.
HTTP Monitor
Production A Acme Shop beta Aina's Pixel 8Status
201 Created
Duration
242 ms
Size
1.4 kB
Response
{ "id": 5812, "sku": "TEA-250", "qty": 2, "total": "RM 37.80" }
Copy as cURL · headers · request body
While you build
See what the app sent and what came back without a cable, a proxy or a certificate on the phone. Copy any call as cURL and replay it.
While it is tested
Watch a tester's phone as they go: its traffic, its screen, its data. Reach in and tap, type, or tell every tester something at once.
Once it ships
Point production builds at their own key. Staging and production never mix, and the same dashboard switches between them.
Features
Everything the app does, where you can see it
One library in the build. Everything below arrives on its own — nothing to configure per screen or per request.
HTTP Monitor
Every request, exactly as it happened
URL, method, status and timing, with the full headers and bodies of both sides — JSON and XML pretty-printed. Captured on the device, batched, and searchable long after the app was closed.
- Filter by method, status, host or any part of the URL
- Copy any call as cURL and replay it in a terminal
- Redact Authorization, cookies or any header before it leaves the phone
- A viewer inside the app too, for when there is no dashboard at hand
-H 'Content-Type: application/json' \
-H 'Authorization: ‹redacted›' \
--data '{"cart":5812}'
Mocking
See the error screen without breaking the server
Make a rule, and a matching call answers the way you say — a 503, an empty list, a malformed body — on the phones you choose, while the backend carries on untouched. Or hold the call a few seconds, or fail it as a dead network would.
- Respond with any status, headers and body, or delay, or fail
- Start one from any captured request — Mock this response
- One device, one build, or every build — switched on and off in a click
- Ends after an hour unless kept; production asks you to confirm
Device Control
Drive the app on someone else's phone
Ask any install for a screenshot of the app, then click it to tap the phone. Every tap comes back as a fresh screenshot of what it did — and shows on the phone too, so whoever holds it sees exactly where you pressed.
- Tap and long-press, press Back, type into whatever field has focus
- Enter, Tab, Escape and Backspace, one click each
- Sees only the app's own window — no permission to grant
- Quiet when idle, quick the moment you start driving
Navigation
Keyboard
Database & Shared Preferences
Its data, live, without pulling a file
Run SQL against the app's own SQLite databases on the phone, and read or change its SharedPreferences in place — the app sees a change the moment it is made, and the page says whether each one took.
- Reads are always safe: the database opens read-only for them
- A change needs a fresh backup first — taken in one click
- Back up the databases and preferences files you choose, kept 30 days
- Open any backup in a read-only SQL console, or download it
FROM cart_items WHERE qty > 1;
| sku | qty | updated_at |
|---|---|---|
| TEA-250 | 2 | 10:42:07 |
| CUP-SET | 3 | 10:44:51 |
| HONEY-500 | 2 | 10:46:13 |
Notifications
Tell every tester at once
Type a message and it pops up inside the app on every install you choose — over whatever screen is up. Send a picture to look at, or a file to open: a new build to install, say.
- Text, an image to enlarge, or a file to open
- Delivery tracked per device: seen, waiting, or never collected
- Held for an app in the background, shown when it comes forward
Build 3.5 is ready
Checkout is fixed. Please install and try a card payment again.
Overview
How the app is doing, at a glance
Request volume, failure rate and latency over the last hour, day, week or month — against the period before — with the slowest endpoints and busiest hosts named. For one device, one build, or all of them.
- The average response time, and the time 95% of requests finish within — so a slow few can't hide
- Every device's online status, kept up to date on its own
- Refreshes live while you watch
Requests
48.2k
+12%
Failed
1.8%
−0.6 pt
95% within
412 ms
+38 ms
Crashes & Logs
"It crashed" — now with the stack trace
A crash is kept on the phone and sent the moment the app starts again, grouped with every other crash that failed the same way — and shown beside the requests and log lines that led up to it. Log lines stream to a live page per device.
- The same crash across devices and versions, counted as one
- The requests and logs from the two minutes before it
- Resolve it — it reopens by itself if it happens again
- A live log tail, filtered by level, tag or text
java.lang.IllegalStateException: Cart is empty at shop.acme.Cart.pay(Cart.kt:42) at shop.acme.CheckoutActivity.onClick(CheckoutActivity.kt:88) at android.view.View.performClick(View.java:7659)
10:46:12.408 I Checkout: Paying 37.80
10:46:12.913 W Payment: Card needs 3-D Secure
10:46:13.120 E Checkout: Cart was emptied mid-payment
Staging & production
Two keys, one dashboard, nothing mixed up
Every project has a staging key and a production key. The key a build reports with decides where its traffic lands; the top bar switches between them. Apps name themselves from their own build — tag a build to tell a white-label or a tester's copy apart.
ghost_Ck7g••••••••••••wLGc
ghost_HNtH••••••••••••wfBj
Apps name themselves
Package, label and icon are read off the build. Nobody types a package name into the dashboard.
Tags for every variant
A white-label build or a per-tester copy becomes its own app with one argument.
Online, or not
A heartbeat a minute keeps each device's status honest, at a few kilobytes an hour.
Your server, your data
Self-hosted. Traffic goes from the phone to you, and nowhere else.
Secrets stay on the phone
Headers you name are redacted before anything is stored or sent.
Kind to data plans
Batched every 15 seconds, bodies capped, and only the backups you pick.
How it works
Up and reporting in three steps
Kotlin or Java, OkHttp or HttpURLConnection. Kept out of release builds unless you want it there.
-
1
Add the library
From the Ghost Spector Maven repository, as a debug-only dependency of the app module — so none of it reaches a release build unless you ask.
// settings.gradle.kts repositories { maven { url = uri("https://maven.ghostspector.com") } } // app/build.gradle.kts dependencies { debugImplementation("com.ghostspector:ghostspector:2.0.1") }
-
2
Start it, and point it here
Once, in Application.onCreate, with the key your project was given — the staging key for a staging build. Turn on every feature, or only the ones you want. Then add the interceptor to your OkHttp client; Retrofit is covered with it.
GhostSpector.with(this, BuildConfig.GHOST_ENDPOINT, BuildConfig.GHOST_STAGING_KEY) .redactHeaders("Authorization", "Cookie") .enableAllMonitor() // or .enableHttpMonitor(), .enableDeviceControl(), … .start() val client = OkHttpClient.Builder() .addInterceptor(HttpMonitorInterceptor()) .build()
-
3
Open the dashboard
The app and its device appear on their first batch — named, with the app's own icon. Pick them in the top bar and every page follows.
Environment StagingApp Acme ShopTag betaDevice Aina's Pixel 8
Your app's traffic is already on its way
Sign in to see what your builds are doing right now — every request, every device, both environments.