docs: RE report + correct the privacy writeup from live analysis

Adds OFFICIAL_APP_REVERSE_ENGINEERING.md — a technical teardown (methodology,
auth model, device-ID, GPS, Instabug) with mermaid diagrams and the mitmproxy
capture timeline.

Corrects OFFICIAL_APP_PRIVACY.md, which was wrong on two counts:
- IMEI: the app reads no IMEI; it sends the Android SSAID to /api/Device in a
  field misleadingly named IMEINumber.
- Trackers: Segment/Amplitude/Firebase-Analytics/Sentry/ad-ID are all ABSENT;
  the real telemetry SDK is Instabug (session replay, screenshots, network logs).
Also folds in the confirmed GPS auto-send (with the GrapheneOS caveat).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-16 19:11:26 +00:00
parent 2bdb59b49a
commit 2434879804
2 changed files with 371 additions and 84 deletions

View file

@ -4,111 +4,119 @@
(`com.ipsgroupinc.parksmarter`, version 4.4.0), and how **BigBrainParking** — this (`com.ipsgroupinc.parksmarter`, version 4.4.0), and how **BigBrainParking** — this
open-source alternative — is different.* open-source alternative — is different.*
This was written by inspecting the official app's own files (its permission list and This was written by taking the official app apart: reading its program code, and
its program code). It's meant to be readable by anyone, not just programmers. Where we're watching its real network traffic through a proxy. Where we're **sure**, we say
**sure**, we say so; where we're making an educated guess, we say that too. so; where something is situational, we say that too. (For the technical teardown
with diagrams and evidence, see
[OFFICIAL_APP_REVERSE_ENGINEERING.md](OFFICIAL_APP_REVERSE_ENGINEERING.md).)
--- ---
## The short version ## The short version
- **Your exact location is sent to ParkSmarter's servers whenever you look at the map** - **Your exact location is sent to ParkSmarter's servers when you use the map**
in the official app — automatically, just by opening it, not only when you ask it to to find meters — and the app is built to do this **automatically** when the map
"find parking near me." opens, not only when you ask it to "find parking near me."
- **The app also carries a stack of tracking tools** (Segment, Amplitude, Firebase, - **The app carries one heavy tracking tool — Instabug** — that can record your
Sentry) that quietly report what you do and your device's identity to outside companies. screen, take screenshots, and log the app's network activity, reporting to an
- **It can read a permanent hardware ID from your phone and send it to ParkSmarter.** outside company.
- **It sends a permanent per-device ID to ParkSmarter.** It is *not* your phone's
IMEI (despite the app calling the field `IMEINumber`) — it's Android's device
ID. Still a fixed code that identifies your specific phone to ParkSmarter.
- **Good news:** it **cannot** track your location while the app is closed. - **Good news:** it **cannot** track your location while the app is closed.
- **BigBrainParking removes the tracking entirely** and only sends your location when - **BigBrainParking removes the tracking entirely** and only sends your location
*you* deliberately tap "My location" and search. when *you* deliberately tap "My location" and search.
--- ---
## First, why does this matter? ## First, why does this matter?
"Location data" is a record of where you physically are. On its own, one location isn't "Location data" is a record of where you physically are. On its own, one location
a big deal. But a *stream* of them — collected quietly and often — reveals where you live, isn't a big deal. But a *stream* of them — collected quietly and often — reveals
work, worship, and who you visit. When that's combined with a permanent ID for your phone where you live, work, worship, and who you visit. Tie that to a permanent ID for
and shared with advertising/analytics companies, it becomes a profile of you that can be your phone and it becomes a profile of you that can be sold, leaked, or
sold, leaked, or subpoenaed. So the questions that matter are: **who collects it, how subpoenaed. So the questions that matter are: **who collects it, how often, is it
often, is it tied to your identity, and does it leave in ways you didn't ask for?** tied to your identity, and does it leave in ways you didn't ask for?**
--- ---
## When does the official app send your location to ParkSmarter? ## When does the official app send your location to ParkSmarter?
**To ParkSmarter's own servers, your GPS is used for one thing: finding nearby meters.** **To ParkSmarter's own servers, your GPS is used for one thing: finding nearby
There are exactly two requests that carry your coordinates (finding meters near a point); meters.** We confirmed this by watching the traffic: the coordinates go out as a
your location is **not** attached to logging in, starting a parking session, or paying. `GET …/api/Meter?Lat=…&Long=…` request, and your location is **not** attached to
That part is reasonable. logging in, starting a session, or paying. That part is reasonable.
**The catch is *when* it happens.** The official app is built to grab your live GPS and **The catch is *when*.** The app is built to grab your live GPS and ask "what
ask ParkSmarter "what meters are near me?" **automatically when you open the map** — you meters are near me?" **automatically when the map opens** — no tap required — as
don't have to tap anything. So simply browsing the app quietly sends your precise location long as you're logged in and have granted location permission. So simply browsing
to ParkSmarter's servers. the map quietly sends your precise location to ParkSmarter.
> **How sure are we?** *Fairly sure.* The code clearly reads your current GPS and has a > **One nuance we saw firsthand:** on a de-Googled phone (GrapheneOS), the map
> "nearby meters" feature wired to the map. Confirming the exact moment it fires would take > itself doesn't draw (it needs Google services), which can suppress that
> watching the app's live network traffic, which we haven't done yet. > automatic send. On a normal Google phone the auto-send happens as designed.
**BigBrainParking does the opposite on purpose:** it opens on your **last parking lot** **BigBrainParking does the opposite on purpose:** it opens on your **last parking
(from your own history, not your GPS), and it only sends your location when you *explicitly* lot** (from your own history, not your GPS), and it only sends your location when
tap **"My location"** and then search. you *explicitly* tap **"My location"** and then search.
--- ---
## The bigger privacy concerns ## The bigger privacy concerns
These are things the official app *can* do beyond the parking API itself. They're taken ### 1. A heavy tracking tool: Instabug
from the app's official permission list and the tracking libraries built into it.
### 1. Hidden tracking/analytics companies (the biggest one) The app bundles **Instabug**, a monitoring/bug-reporting toolkit — and it's the
serious one. It's capable of:
The app bundles four separate tracking/telemetry toolkits: - **recording your session / replaying what you did**,
- **taking screenshots** of the app,
- **logging the app's network requests**,
- **tracking your taps and the steps you take** through the app.
- **Segment** — a "customer data platform." Its whole job is to collect what you do in the All of this reports to an outside company's servers (`api.instabug.com`). We saw
app and fan it out to many other companies. it phone home the moment the app started.
- **Amplitude** — product-analytics; records your actions to study behavior.
- **Firebase** (Google) and **Sentry** — Google's analytics/crash tools and an error tracker.
These typically collect: **which screens you visit and when, your device model, and your > **What it is *not*:** we specifically checked and the app does **not** contain
IP address** (which reveals your rough city/neighborhood), and often an **advertising ID** > Segment, Amplitude, Sentry, Google Analytics/Firebase-Analytics, an advertising
(a code used to follow you across different apps). Even without your exact GPS, this is a > ID, or Play Store install-tracking. (An earlier version of this note listed
detailed behavioral profile shared with outside companies. > those — that was wrong; Instabug is the actual story.) Google is present only
> for **push notifications**.
> **BigBrainParking has none of these.** No Segment, no Amplitude, no Firebase, no Sentry, > **BigBrainParking has none of this.** No Instabug, no analytics, no ad-ID, no
> no ad-ID, no Google services at all. > Google services at all.
### 2. A permanent hardware ID sent to ParkSmarter ### 2. A permanent device ID sent to ParkSmarter
The app is allowed to **read phone/device identifiers** (the "read phone state" When you log in, the app sends ParkSmarter a fixed identifier for your phone,
permission), and it has a feature that sends your device's **IMEI number** — a permanent inside a request to `…/api/Device`. The field is **named `IMEINumber`**, which is
serial number for your phone — to ParkSmarter. Unlike a password you can change, this ID misleading — **it is not your IMEI**. Modern Android forbids apps from reading the
is fixed, so it can be used to recognize your specific phone forever. IMEI at all, and this app never tries. The value it actually sends is your
phone's **Android device ID** (a code assigned to your device). Unlike a password,
it doesn't change, so it can be used to recognize your specific phone over time.
It goes only to ParkSmarter, not to a third party.
> **BigBrainParking never reads or sends this.** > **BigBrainParking never reads or sends any device ID.**
### 3. Other capabilities worth knowing about ### 3. Other capabilities worth knowing about
- **Install tracking** — records how you found/installed the app (via the Google Play - **Microphone & camera permissions** — camera is for scanning kiosk QR codes;
"install referrer"), tied to your Play account. microphone is unusual for a parking app (may be an unused library leftover).
- **Wi-Fi scanning permission** — can see the Wi-Fi networks around you, which can be used - **Wi-Fi & phone-state access** — used by a networking library to report your
to *estimate your location even without GPS*. connection type and carrier name; it does **not** read your IMEI.
- **Microphone permission** — unusual for a parking app. It may be an unused leftover from - **Bluetooth** — for talking to parking meters directly.
a library, but the app is allowed to access your microphone. - **Google push messaging (FCM)** — notifications route through Google.
- **Google push messaging (FCM)** — notifications are routed through Google, which links
your device to Google and to ParkSmarter's messaging system.
--- ---
## To be fair — what it does *not* do ## To be fair — what it does *not* do
- **No background location tracking.** This is important: the app can only see your - **No background location tracking.** The app has the everyday location
location **while you have it open and are using it**. It has the everyday location permission but **not** the "all the time / background" one, so it can't follow
permission but **not** the special "all the time / in the background" one, so it can't you when it's closed.
follow you around when the app is closed. - **No IMEI, no advertising ID, no Segment/Amplitude/Sentry.**
- Your location going to ParkSmarter is genuinely limited to the "find nearby meters" - Your location going to ParkSmarter is genuinely limited to the "find nearby
feature — it isn't stapled onto payments or your account details. meters" feature — it isn't stapled onto payments or your account details.
--- ---
@ -116,12 +124,13 @@ is fixed, so it can be used to recognize your specific phone forever.
| | Official ParkSmarter app | BigBrainParking | | | Official ParkSmarter app | BigBrainParking |
| --- | --- | --- | | --- | --- | --- |
| Sends GPS to ParkSmarter | **Automatically** when you open the map | **Only** when you tap "My location" and search | | Sends GPS to ParkSmarter | **Automatically** when the map opens | **Only** when you tap "My location" and search |
| Opens the map on… | Your current GPS location | Your **last parking lot** (not your GPS) | | Opens the map on… | Your current GPS location | Your **last parking lot** (not your GPS) |
| Hidden analytics/tracking SDKs | Segment, Amplitude, Firebase, Sentry | **None** | | Third-party tracking SDKs | **Instabug** (session replay, screenshots, network logs) | **None** |
| Advertising ID / cross-app tracking | Likely collected | **Never** | | Advertising ID / cross-app tracking | **No** (none present) | **Never** |
| Sends a permanent device ID (IMEI) | Yes (feature exists) | **Never** | | Sends a permanent device ID | **Yes** — Android device ID, sent as `IMEINumber` | **Never** |
| Google services required | Yes (Firebase, FCM) | **None** (runs on GrapheneOS) | | Reads your hardware IMEI | **No** (not possible on modern Android) | **Never** |
| Google services required | Yes (push notifications) | **None** (runs on GrapheneOS) |
| Background location | No | No | | Background location | No | No |
| Source code you can inspect | No (closed) | **Yes** (this repo) | | Source code you can inspect | No (closed) | **Yes** (this repo) |
@ -129,18 +138,19 @@ is fixed, so it can be used to recognize your specific phone forever.
## How we checked (for the curious) ## How we checked (for the curious)
- **Permissions** come from the app's own `AndroidManifest` (the official list Android - **Permissions** come from the app's own `AndroidManifest`. Confirmed present:
shows). Confirmed present: precise + approximate location, read-phone-state, Wi-Fi precise + approximate location, read-phone-state, Wi-Fi, microphone, camera,
state, microphone, camera, Google push, install-referrer. Confirmed **absent**: Bluetooth, Google push. Confirmed **absent**: background location.
background location. - **The device-ID and location behavior** were read from the app's decompiled
- **Tracking libraries** and the **location-bearing requests** (`getMetersByLocation` / code (it's a React Native / Expo app, so the logic is in a JavaScript bundle)
`getLimitedMetersByLocation`) and the **IMEI-sending** device-registration request come **and confirmed on the wire** with a live proxy capture: the `…/api/Device`
from reading the app's decompiled program code (version 4.4.0). request carrying the Android ID as `IMEINumber`, and the `…/api/Meter?Lat=…`
- **Educated guesses** (clearly labeled above) are the auto-send-on-open behavior and the request carrying GPS.
advertising-ID collection — code strongly suggests them, but a live network capture would - **The SDK list** (Instabug present; Segment/Amplitude/Sentry/Firebase-Analytics/
make them certain. We can do that capture if you want proof. ad-ID absent) comes from searching the app's bundled code and its network
endpoints.
*Not affiliated with or endorsed by IPS Group / ParkSmarter. This is an independent, *Not affiliated with or endorsed by IPS Group / ParkSmarter. This is an
good-faith analysis of a publicly distributed app for the purpose of building a independent, good-faith analysis of a publicly distributed app for the purpose of
privacy-respecting alternative. Findings reflect version 4.4.0 and could change in later building a privacy-respecting alternative. Findings reflect version 4.4.0 and
versions.* could change in later versions.*

View file

@ -0,0 +1,277 @@
# Reverse-engineering the official ParkSmarter app
A technical teardown of the official **Park Smarter** Android app
(`com.ipsgroupinc.parksmarter`, **v4.4.0**, versionCode 170), focused on **what
data leaves the device, when, and to whom** — with special attention to the
**device identifier** and **GPS location**. Combines static decompilation with a
live man-in-the-middle capture.
> Independent, good-faith security/privacy research on a publicly distributed
> app, for the purpose of building a privacy-respecting alternative
> (BigBrainParking). Not affiliated with or endorsed by IPS Group. Findings
> reflect v4.4.0. The full decompiled source is kept in a **private** mirror; this
> report quotes only the excerpts needed as evidence.
---
## TL;DR
| Claim | Verdict | Evidence |
|---|---|---|
| The app reads your hardware **IMEI** | **False** | No `TelephonyManager.getImei/getDeviceId` anywhere; `react-native-device-info` isn't even bundled; and Android 10+ blocks IMEI for normal apps regardless. |
| The app sends a **persistent device ID** to its backend | **True** | `PUT /api/Device` carries the **Android SSAID** in a field misleadingly named `IMEINumber`, plus the push token as `DeviceID`. |
| The app sends your **GPS** to its servers | **True**, for meter search only | `GET /api/Meter?Lat=…&Long=…`; captured live. Not attached to login/session/payment. |
| GPS is sent **automatically** on map open | **True in code**, situational at runtime | Wired to map-ready when authenticated + permission granted; on GrapheneOS the map fails to render (needs Play Services), which suppresses the auto-fetch. |
| Bundles Segment / Amplitude / Firebase-Analytics / Sentry | **False** | None present. |
| Bundles heavy telemetry | **True** | **Instabug** — session replay, screenshot capture, network-request logging, visual user-steps → `api.instabug.com`. |
| Advertising ID / install-referrer | **False** | Neither is present. |
---
## Methodology
Both static and dynamic analysis were used.
```mermaid
flowchart LR
A["Park+Smarter_4.4.0.xapk<br/>(base + 3 config splits)"] --> B[jadx 1.5.1<br/>dex → Java]
A --> C["hermes-dec<br/>index.android.bundle → JS"]
A --> D[apktool 2.10.0<br/>manifest + resources]
A --> E["APKEditor merge → 1 universal APK<br/>+ apk-mitm (trust user CA)"]
E --> F["mitmproxy on device<br/>(live HTTPS capture)"]
B --> G[(Findings)]
C --> G
D --> G
F --> G
```
- **jadx** decompiled the dex → Java. This is only the RN framework + native
modules + SDKs; the app's own logic is **not** here.
- The app is **React Native / Expo SDK 53**, so its business logic ships as
**Hermes bytecode** (`assets/index.android.bundle`, Hermes v96). **hermes-dec**
recovered readable pseudocode (`decomp.js`) with de-obfuscated function names.
- **apktool** decoded the manifest and resources.
- For the live capture, the split APKs were merged into one universal APK
(APKEditor) and repackaged to trust a user CA (`network_security_config`), then
driven through **mitmproxy**. (Merging first was necessary: patching only the
base split renumbers its resource IDs and desyncs the untouched `config.hdpi`
split, which crashes the app on the first text field.)
---
## App architecture & network model
Expo SDK 53 RN app. All API traffic goes through one request builder that stamps
these headers on **every** call:
| Header | Value | Notes |
|---|---|---|
| `Application_Token` | `B66EEDDA-…` (static) | App-wide, embedded in the bundle |
| `X-Request-Id` | random UUID | Per request |
| `Auth_Token` | rolling | Rotates on each response |
| `ParkSmarter_SessionId` | per session | |
| `Content-Type` | `application/json` | POST/PUT only |
| `User-Agent` | `okhttp/4.12.0` | RN's HTTP stack |
Five hard-coded environments (bundle string literals):
- Prod: `https://apiv2.parksmarter.com`, `https://apiv3.parksmarter.com`
- Staging: `https://staging-parksmarter-api.ipsmeters.com`
- Testing: `https://testing-parksmarter-api.ipsmeters.com`
No TLS pinning is configured (okhttp's `CertificatePinner` class is present but no
pins are set), and the manifest declares no `networkSecurityConfig` — which is why
a stock install can't be MITM'd without repackaging.
---
## Finding 1 — Device identifier: the "IMEINumber" that isn't an IMEI
The app defines a Redux thunk literally named `device/setIMEINumberAsyncThunk`.
Despite the name, it **never reads an IMEI**. It reads the **Android SSAID**
(`Application.androidId` from `expo-application`), caches it in secure storage as
`psUUID`, and later transmits it.
Evidence (`decomp.js`):
```js
// getAndroidId → expo-application's Application.androidId (the SSAID)
r0 = r0.default; r0 = r0.androidId; return r0;
// stored under 'psUUID'
r2 = 'psUUID'; r2 = setObjectAsync.bind(...)('psUUID', androidId);
// later placed on the wire:
r2['DeviceID'] = pushNotificationsToken;
r2['IMEINumber'] = imeiNumber; // === Application.androidId (SSAID)
r2['DeviceType'] = '1';
// endpoint: putUpdateDeviceToken → { url: '/api/Device' }
```
Confirmations:
- **No real IMEI read.** Zero `getImei` / `TelephonyManager.getDeviceId` /
`getSubscriberId` / `getSimSerialNumber` in the Java **or** the bundle. Every
`getDeviceId()` match in the Java is unrelated — `MotionEvent`/`KeyEvent`
device IDs (input routing) or `Context.getDeviceId()` (the API-34 *virtual*
device id, an `int`). `react-native-device-info` (which could read IMEI) isn't
bundled at all.
- **`READ_PHONE_STATE`** comes from `@react-native-community/netinfo`, which uses
`TelephonyManager` only for `getNetworkOperatorName()` (carrier name) and
network type — not identity.
- **Modern Android blocks it anyway**: since Android 10, `getImei()` throws for
non-privileged apps regardless of the permission.
```mermaid
sequenceDiagram
participant App
participant OS as Android OS
participant PS as apiv2.parksmarter.com
Note over App: cold start / login / token change
App->>OS: Application.androidId (SSAID)
OS-->>App: e.g. "a1b2c3…"
App->>App: cache as psUUID (secure store)
App->>PS: PUT /api/Device<br/>{ IMEINumber: SSAID, DeviceID: pushToken, DeviceType: "1" }
Note over PS: a stable per-device ID,<br/>tied to the account — just not the IMEI
```
**Net:** the app does transmit a persistent, per-device identifier to its own
backend, mislabeled `IMEINumber`. It is the SSAID, not the hardware IMEI, and it
is **not** sent to any third party. The earlier "sends your IMEI" claim was wrong
in substance but pointed at something real — this field.
---
## Finding 2 — GPS location
Location is acquired via **expo-location** (`getLastKnownPositionAsync` +
`watchPositionAsync` at `Accuracy.Balanced`) and sent to the backend **only** on
the meter-search endpoints:
```
GET /api/Meter?Lat=<lat>&Long=<long>&localeCode=en-US
```
The query carries **only** `Lat`/`Long` (no radius/limit); "all vs limited meters"
is an endpoint choice, not a parameter. Location is **not** attached to login,
session load, or payment — confirmed both in code and in the live capture.
**Auto-send on map open.** The map hub wires `useAutoGoToUserLocation`, a state
machine that — when `isAuthenticated && isMapReady && locationPermissionGranted`
and the app is foreground — recenters on the user's GPS and fires the meter query
**without any tap**. Panning/zooming fires more (debounced).
```mermaid
sequenceDiagram
participant User
participant Map as Map screen
participant Loc as expo-location
participant PS as apiv2.parksmarter.com
User->>Map: open Map (logged in, permission granted)
Map->>Loc: watchPositionAsync (Balanced)
Loc-->>Map: {lat, long}
Map->>PS: GET /api/Meter?Lat=…&Long=… (auto, no tap)
User->>Map: pan / zoom
Map->>PS: GET /api/Meter?Lat=…&Long=… (debounced)
```
**GrapheneOS caveat (observed).** `react-native-maps` needs Google Play Services
for tiles; on GrapheneOS the map doesn't render, so `isMapReady` may never flip
and the auto-fetch can be suppressed. In the live capture the single coordinate
sent (`Lat=48.274147628&Long=-116.550122619`) exactly matched a *known lot*
location rather than a fresh arbitrary GPS fix — consistent with the map being
degraded. Location egress is proven; the fully-automatic-on-open behavior is a
property of the code that a Play-Services device would exercise more visibly.
**BigBrainParking, by contrast**, never sends GPS to the API except when you
explicitly tap "My location" and search; the map opens on your last lot from
history, not your GPS.
---
## Finding 3 — Third-party telemetry: Instabug (and only Instabug)
Contrary to the earlier analysis, **Segment, Amplitude, Sentry, Firebase
Analytics, Crashlytics, advertising-ID, and Play install-referrer are all
absent** (verified by package-dir and endpoint-host search). Firebase is present
only as **Cloud Messaging** (push), not analytics.
The one real telemetry SDK is **Instabug** (2,328 classes), and its scope is
broad:
- **Session replay** (`library/sessionreplay`)
- **Screenshot / screen capture** (`instacapture`, `screenshot`)
- **Network-request logging & interception** (`apm/networking`, `networkinterception`)
- **Visual user-steps / interaction tracking** (`visualusersteps`, `interactionstracking`)
- Crash reports, APM, surveys, user attributes
All reporting to `api.instabug.com`. Captured live on startup:
`POST /api/sdk/v3/sessions/v2`, `GET /api/sdk/v3/features`, `/api/sdk/v3/first_seen`.
```mermaid
flowchart TD
D[Your device]
D -->|"SSAID as IMEINumber + push token<br/>GPS (Lat/Long) on meter search<br/>account, vehicle, payment, sessions"| PS[apiv2.parksmarter.com]
D -->|"session replay, screenshots,<br/>network logs, user-steps, crashes"| IB[api.instabug.com]
D -->|push registration| FCM[Firebase Cloud Messaging]
style PS fill:#294,color:#fff
style IB fill:#922,color:#fff
style FCM fill:#247,color:#fff
```
---
## Permissions (from the manifest)
Present: `ACCESS_FINE/COARSE_LOCATION`, `READ_PHONE_STATE` (netinfo),
`ACCESS_WIFI_STATE`, `CAMERA`, `RECORD_AUDIO`, `BLUETOOTH_SCAN/CONNECT` (meter
BLE), `POST_NOTIFICATIONS`, `RECEIVE_BOOT_COMPLETED`, `FOREGROUND_SERVICE`,
`USE_BIOMETRIC`, `SYSTEM_ALERT_WINDOW`, `DETECT_SCREEN_CAPTURE`, install-referrer
service binding, plus a long list of launcher badge permissions.
**Absent (important):** `ACCESS_BACKGROUND_LOCATION`. The app **cannot** track
location while closed.
---
## Live capture — evidence timeline
Captured through mitmproxy against a repackaged (user-CA-trusting) universal APK,
authenticated session:
```
18:44:07 GET apiv2.parksmarter.com/api/ParkingSession
18:44:07 GET apiv2.parksmarter.com/api/Session
18:44:08 POST api.instabug.com/api/sdk/v3/sessions/v2 (414B) ← telemetry
18:44:08 GET api.instabug.com/api/sdk/v3/first_seen
18:44:16 GET apiv2.parksmarter.com/api/Meter?ZoneName=dl
18:44:17 GET apiv2.parksmarter.com/api/Meter?Lat=48.274…&Long=-116.550… ← GPS
18:44:20 GET apiv2.parksmarter.com/api/ParkingEstimateItems
18:44:33 GET apiv2.parksmarter.com/api/ApplicationValidity
18:44:39 GET apiv2.parksmarter.com/api/ParkSmarterPrivacyPolicies
```
Header sample on a parksmarter request: `Application_Token: B66EEDDA-…`,
`Auth_Token: …` (rolling), `ParkSmarter_SessionId: …`, `User-Agent: okhttp/4.12.0`.
`PUT /api/Device` (the `IMEINumber` PUT) did **not** fire in this session because
the login was cached — it sends on fresh login / token change.
---
## Reproducing this
The repackaged capture-ready APK and step-by-step mitmproxy instructions live in
the private source mirror (`ParkSmarterSourceCode`, `MITMPROXY.md`). In short:
merge splits → inject a user-CA `network_security_config` → resign → install the
CA as a user cert → proxy the phone through mitmproxy (mobile data **off** so the
Wi-Fi proxy applies).
---
## Appendix — backend endpoint inventory (from the bundle)
`ApplicationValidity`, `Auth`, `SignUp`, `User`, `Device`, `Session`,
`ParkingSession`, `Meter` (by location / scanner code / serial / zone name),
`ParkingLots`/`ParkingLogix`, `ParkingEstimateSingle/Multi/Items`,
`StartParkingSession`, `ParkingReceipt`, credit-card + vehicle CRUD,
`NotificationSettings`, `ParkSmarterPrivacyPolicies` / `Terms` / `About` / `FAQ`.
```