70maiclient
A reverse-engineered Python client for the 70mai dashcam cloud API
(eu-api.70mai.com), built from a Postman-exported HTTPS capture of the
official iOS app and cross-checked against a decompiled Android APK
(re/ — jadx/Ghidra output, not part of the library itself).
It's not affiliated with, endorsed by, or supported by 70mai. The underlying API is undocumented and can change without notice.
Used by homeassistant-70mai, a Home Assistant integration built on top of this client.
Install
PyPI project name is 70maiclient (PyPI names can't start with a digit
in a way that's also a valid Python identifier, so the distribution
name differs from the import name below):
pip install 70maiclient
Usage
import uuid
from client70mai import MaiClient
client = MaiClient(uuid=str(uuid.uuid4()).upper())
client.login(email="your-email", plain_password="your-password")
devices = client.get_all_device_for_user()
print(devices)
detail = client.get_dashcam_detail(device_id="your-device-id")
print(detail)
client.logout()
See example.py for the same script standalone, and
console.py for an interactive rich-based CLI that
exercises every endpoint (Italian-language prompts):
pip install "70maiclient[console]"
python3 console.py
Layout
client70mai/client.py—MaiClient, the only class consumers use. Holds the session, the deviceuuid, the authtoken, and one method per API endpoint.client70mai/signing.py—SignatureProviderprotocol plusBanyacKeyMd5SignatureProvider, the verified implementation of the server's request-signing scheme.client70mai/exceptions.py—MaiErrorand subclasses for transport, HTTP, response-parsing, auth, and signing failures.client70mai/__init__.py— public re-exports.example.py— minimal login → list devices → detail → logout script.console.py— interactiverich-based CLI overMaiClientfor manually exercising every endpoint (Italian-language prompts).re/— the APK, extracted dex/native libs, and Ghidra/pyghidra scripts used to reverse the signing algorithm. Not imported byclient70mai, and not shipped in the PyPI package.api-refs/— captured traffic used as ground truth: Postman exports (request-only, no response bodies) and*.proxymanlogv2archives (a zip of one JSON file per request,request/responsebodyDatafields base64-encoded — these DO carry real response bodies, unlike the Postman exports, and are how most "CONFIRMED response" notes inclient.pywere derived). Not shipped in the PyPI package.
Authentication model
There is no Authorization header or cookie anywhere in the captured
traffic. Every authenticated call instead sends, inside its JSON body:
uuid— per-install device UUID (caller-supplied, any valid UUID string).channel— constant3001013(CHANNEL_IDinclient.py) on every request, including unauthenticated ones.ts— client epoch-millisecond timestamp, unique per request.token— opaque session token fromuserLoginPwd/login(), reused verbatim untillogout().sig—md5(token + uuid + channel + ts + SECRET),SECRETrecovered from the Android app's nativelibbanyackey.so(see signing below).
MaiClient._signed_body() assembles this envelope for every method except
login() and get_ts(). Calling any endpoint before login() raises
MaiNotAuthenticatedError.
login() uses a different, separately-verified construction for the
passWord field (md5("dashcam" + plain_password + SECRET)) and its own
sig (md5(deviceType + email + "null" + passWord + ts + SECRET)) — see
the signing.py module docstring for the full decompilation trail.
Response envelope
Every call returns the raw parsed JSON body as Dict[str, Any], matching
the confirmed MaiCommonResult<T> shape:
{"error": bool, "errorCode": int, "errorMessage": str, "resultBodyObject": T}
resultBodyObject's shape (T) is not unwrapped by the client. The
original Postman captures had no successful response bodies at all, so
every schema was originally a best-effort guess; a 2026-09 Proxyman
capture (api-refs/*.proxymanlogv2) since confirmed the real
resultBodyObject shape for many endpoints (each such method's docstring
in client.py says "CONFIRMED response" and gives the shape). Endpoints
without that note are still unconfirmed — treat any field access on their
response as unverified until you observe real traffic. login() is the
one method that always reads a specific field
(resultBodyObject.token), confirmed, because the client cannot function
without it.
Extending the client
- New endpoints follow the existing pattern: build an
extradict of endpoint-specific fields, pass it toself._signed_body(extra), POST viaself._post(path, body). - Do not invent response-field extraction beyond what a real captured response confirms — raise/return the raw dict instead, as every existing method does.
- If you reverse-engineer a new signing variant, add it to
SignatureProvider(protocol) andBanyacKeyMd5SignatureProvider(implementation) rather than hardcoding it inclient.py. - Live view (P2P streaming) is not implemented — static analysis found
getTutkToken/livingendpoints backed by the ThroughTek/TUTK SDK, but no request/response shape was captured or confirmed.
Releasing
Tagging a GitHub Release publishes to PyPI automatically via
.github/workflows/publish.yml (trusted publishing, no stored token).
Bump version in pyproject.toml before tagging.
License
GNU AGPLv3 © Lorenzo De Luca
Metadata
Release files for 70maiclient 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| 70maiclient-0.1.0.tar.gz | 28.0 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 70maiclient-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 52.9 kB
Release files / 70maiclient-0.1.0.tar.gz
| Download URL | 70maiclient-0.1.0.tar.gz |
|---|---|
| Size | 28.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b0664d671e30ff2bdb2d538465a4c5077919189a69d6f7232c454fec956357fa
|
|
BLAKE2b-256 checksum How to use checksums |
805daf816847b26e8525b80ca4f76561ed0222b59b1f76e20f43b2c67aa788df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.
Transparency logRelease files / 70maiclient-0.1.0-py3-none-any.whl
| Download URL | 70maiclient-0.1.0-py3-none-any.whl |
|---|---|
| Size | 24.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a7f26e807ecc0be4d7971a6f4a9c18eed6f1d853122f471cf3bf60710e2ab40a
|
|
BLAKE2b-256 checksum How to use checksums |
9fdc4014a770016a298d2fe901454341e2a2eed08d3d0acae7cd9d2bb1844b62
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.
Transparency logSecurity for 70maiclient 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for 70maiclient-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.