|
All checks were successful
build-apk / build (push) Successful in 28m23s
Countdown notification "nothing appears" on session start: - The small icon was the adaptive launcher mipmap (applicationInfo.icon), which Android 13+/GrapheneOS rejects as an invalid notification small icon and drops the post silently. Ship a proper white-on-transparent vector small icon (bbp_stat_parking) inside the module and use it. - showCountdown now returns a diagnostic string (posted / notifications disabled / exception) instead of swallowing failures; JS logs the branch, permission result, and native outcome to the in-app diagnostics log. - Log the raw active-session payload in refreshSessionStatus (element shape was previously unconfirmed). Time remaining showed "7694 min": - API's TimeRemaining is SECONDS, not minutes. Format it as hours+minutes in SessionDetail; annotate the type. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| test | ||
| .creds.example.json | ||
| .gitignore | ||
| capture-dl.mjs | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| sweep.mjs | ||
| tsconfig.json | ||
parksmarter-client
An unofficial, typed TypeScript client for the ParkSmarter (IPS Group) parking
API. It was reverse-engineered from the official Android app
com.ipsgroupinc.parksmarter 4.4.0 — a React Native / Hermes build — by decompiling
the JS bundle and recovering every endpoint path, header, app token, and
request/response field name.
TypeScript was chosen because the target is "an alternative Android app or web app":
the same package runs unchanged in a browser, in React Native (Expo/bare), and in
Node 18+. It has zero runtime dependencies (uses fetch). If you specifically
want a native-Kotlin client instead, the environments.ts + endpoint table below map
directly onto Retrofit/OkHttp — ask and it can be ported.
Usage guide: for narrative walk-throughs (searching, quoting a price, running a transaction) plus per-endpoint commentary and the behavioral quirks, see
docs/PARKSMARTER_API.md. The tables below are the quick reference.
Install / build
cd parksmarter-client
npm install
npm run build # emits dist/
Import from source (src/index.ts) or the built dist/.
Quick start
import { ParkSmarterClient } from './src';
const ps = new ParkSmarterClient({ environment: 'prodv2' }); // default env
// 1) Bootstrap — seeds a ParkSmarter_SessionId and returns feature flags.
const validity = await ps.getApplicationValidity();
if (validity.Config?.IsInMaintenanceMode) throw new Error('maintenance');
// 2) Log in — Auth_Token + SessionId are stored automatically.
await ps.loginWithPhone({ phoneNumber: '5551234567', password: 'hunter2' });
// 3) Use authenticated endpoints.
const me = await ps.getUserDetail();
const nearby = await ps.getMetersByLocation({ latitude: 40.44, longitude: -79.99 });
// 4) Start a session.
await ps.startParkingSession({
creditCardId: me.CreditCardDetails![0].CCID!,
vehicleId: me.VehicleDetails![0].VehicleID!,
zoneId: nearby.Zones![0].ZoneId!,
spaceId: nearby.Zones![0].Spaces![0].SpaceID!,
customerId: nearby.Zones![0].CustomerID!,
meterTypeId: nearby.Zones![0].MeterTypeId!,
startTime: new Date().toISOString(),
endTime: new Date(Date.now() + 3600_000).toISOString(),
minutesToPurchase: 60,
parkingCost: 2.0,
transactionFee: 0.35,
});
React Native / Node < 18
Pass a fetchImpl if the global fetch isn't present, and use persistent storage
for tokens:
import { ParkSmarterClient, TokenStore } from './src';
import * as SecureStore from 'expo-secure-store';
const tokens: TokenStore = {
getAuthToken: () => SecureStore.getItemAsync('ps_auth'),
setAuthToken: (t) =>
t ? SecureStore.setItemAsync('ps_auth', t) : SecureStore.deleteItemAsync('ps_auth'),
getSessionId: () => SecureStore.getItemAsync('ps_session'),
setSessionId: (s) =>
s ? SecureStore.setItemAsync('ps_session', s) : SecureStore.deleteItemAsync('ps_session'),
};
const ps = new ParkSmarterClient({ tokens /*, fetchImpl: fetch */ });
Authentication model
This API does not use OAuth/Bearer. Auth is carried in custom headers:
| Header | Meaning | Source |
|---|---|---|
Application_Token |
Identifies the app build. Required on every request. | Per-environment constant (baked in). |
X-Request-Id |
Fresh UUID per request. | Generated by the client. |
Auth_Token |
The logged-in user's token. | Returned in the body of POST /api/Auth as Auth_Token; sent as a header on authenticated calls. |
ParkSmarter_SessionId |
Server session id. | Seeded by GET /api/ApplicationValidity and login; echoed on some responses. |
Content-Type: application/json |
On POST/PUT only. | — |
The client persists Auth_Token and SessionId into the TokenStore automatically.
Every request also carries a localeCode query param (default en).
Note: a
POST /api/Authreturning HTTP 201 is treated by the app as a "needs verification / not a success" case, not a normal login. Handle 201 distinctly if you see it.
Environments
| Name | Base URL | Notes |
|---|---|---|
dev |
https://dev-parksmarter-api.ipsmeters.com |
|
stage |
https://staging-parksmarter-api.ipsmeters.com |
|
test |
https://testing-parksmarter-api.ipsmeters.com |
|
prodv1 |
https://api.parksmarter.com |
|
prodv2 |
https://apiv2.parksmarter.com |
default |
prodv3 |
https://apiv3.parksmarter.com |
Application tokens for each are in src/environments.ts.
Endpoint reference
Every method maps to one endpoint. Method is inferred from the app's naming.
| Client method | HTTP | Path |
|---|---|---|
getApplicationValidity() |
GET | /api/ApplicationValidity |
loginWithPhone() / loginWithApple() |
POST | /api/Auth |
logoutAllDevices() |
POST | /api/Auth/Logout |
signUp() |
POST | /api/User |
getUserDetail() |
GET | /api/User |
updateProfile() |
PUT | /api/User |
requestDeleteUser() |
DELETE | /api/User |
isEmailRegistered() / isPhoneRegistered() |
GET | /api/User |
requestResetPassword() |
POST | /api/Password |
updatePassword() |
PUT | /api/Password |
requestVerifyUser() |
POST | /api/UserVerification |
verifyUser() |
GET | /api/UserVerification |
updateDeviceToken() |
PUT | /api/Device |
addVehicle() |
POST | /api/Vehicle |
updateVehicle() |
PUT | /api/Vehicle |
deleteVehicle() |
DELETE | /api/Vehicle |
addCard() / updateCard() |
POST | /api/Card |
setCardDefault() |
PUT | /api/Card |
deleteCard() |
DELETE | /api/Card |
getMetersByLocation() |
GET | /api/Meter (Lat,Long) |
getLimitedMetersByLocation() |
GET | /api/MeterList (Lat,Long) |
getMetersByZoneName() / searchMetersByZoneOrSpace() |
GET | /api/Meter (ZoneName/Query) |
getMetersBySerialNumber() / getMetersByScannerCode() |
GET | /api/Meter (TerminalSerNo/ScannerCode) |
getParkingLots() |
GET | /api/ParkingLogix |
getParkingEstimateMulti() |
GET | /api/ParkingEstimateMulti |
getParkingEstimateSingle() |
GET | /api/ParkingEstimate |
getParkingEstimateItems() |
GET | /api/ParkingEstimateItems |
startParkingSession() |
POST | /api/Session |
getActiveParkingSessions() |
GET | /api/ParkingSession |
getPastParkingSessions() |
GET | /api/Session |
getParkingReceipt() |
GET | /api/ParkingReceipt |
emailParkingReceipt() |
POST | /api/ParkingReceipt |
getNotificationSettings() |
GET | /api/Setting |
setNotificationSettings() |
POST | /api/Setting |
getStates() |
GET | /api/State |
getAbout() / getFAQ() / getPrivacyPolicy() / getTerms() |
GET | /api/ParkSmarter* |
Field-name conventions
The server uses PascalCase ("PSJSON"). This client accepts friendly camelCase inputs
and maps them to the wire format for you; responses are returned as the raw server
PascalCase JSON and typed accordingly in src/types.ts. Response interfaces include
an index signature because not every optional field is guaranteed on every call — the
well-known fields are typed explicitly.
Accuracy & verification status
Request shapes were recovered from the app code and are exact. Response models were then
verified against production by logging in and calling every read-only endpoint (see
sweep.mjs, which records field-names+types only — no PII). Each interface in
src/types.ts is annotated CONFIRMED or UNCONFIRMED.
- CONFIRMED via live capture: login (
AuthResponse),UserDetail,VehicleDetail,CreditCardDetail,Zone/Space/SpacePolicy,ParkingLot/ParkingLotDetail, all three estimate responses (ParkingDetailprice ladder), notification-settings envelope, states wrapper, the password-reset flow, the sharedResponseenvelope, and — from a real DL-zone session —PastSessionandParkingReceipt. - UNCONFIRMED:
ActiveSession(no active session existed on the account at capture time — field names are from static analysis; capture while parked to confirm). capture-dl.mjsrecords a full DL-zone request/response set;sweep.mjsrefreshes the PII-free schema skeletons.
Behaviors worth knowing (all confirmed live — full list in
docs/PARKSMARTER_API.md):
- 200-on-error. Failed login, declined payment, and some bad requests return HTTP
200 with
Response.Status: "Error". Check the envelope, not just the status code. - Meter/estimate search requires auth.
/api/Meter,/api/MeterList, and estimates 401 without a validAuth_Token, despite being data lookups. - Rolling token refresh. A non-empty
Response.Auth_Token(or top-level) replaces your user token — the client stores it automatically. - Stale token ≠ 401. An expired token makes
GET /api/Userreturn an empty body (not a 401); validate by requiring real user fields before trusting the session. - Estimate quirks. Multi is unsupported on some zones (falls back to single);
MaxTime: 0/$0.00means a free window; some zones are flat-rate.
To capture the remaining UNCONFIRMED models, re-run sweep.mjs on an account that has a
saved card and session history. There's no TLS pinning, so a proxy capture on a rooted
device/emulator is also an option if you'd rather see the app's own traffic.
Legal
For interoperability/research with your own account. Not affiliated with or endorsed by IPS Group / ParkSmarter. Respect their Terms of Service and applicable law.