A Python client for the FMD (Find My Device) server API
Project description
fmd_api: Python client for FMD (Find My Device)
Modern, async Python client for the open‑source FMD (Find My Device) server. It handles authentication, key management, encrypted data decryption, location/picture retrieval, and common device commands with safe, validated helpers.
Install
- Requires Python 3.8+
- Stable (PyPI):
pip install fmd_api
- Pre‑release (Test PyPI):
pip install --pre --index-url https://test.pypi.org/simple/ \ --extra-index-url https://pypi.org/simple/ fmd_api
Quickstart
import asyncio, json
from fmd_api import FmdClient
async def main():
# Recommended: async context manager auto-closes session
async with await FmdClient.create("https://fmd.example.com", "alice", "secret") as client:
# Request a fresh GPS fix and wait a bit on your side
await client.request_location("gps")
# Fetch most recent locations and decrypt the latest
blobs = await client.get_locations(num_to_get=1)
loc = json.loads(client.decrypt_data_blob(blobs[0]))
print(loc["lat"], loc["lon"], loc.get("accuracy"))
# Take a picture (validated helper)
await client.take_picture("front")
asyncio.run(main())
TLS and self-signed certificates
Find My Device always requires HTTPS; plain HTTP is not allowed by this client. If you need to connect to a server with a self-signed certificate, you have two options:
- Preferred (secure): provide a custom SSLContext that trusts your CA or certificate
- Last resort (not for production): disable certificate validation explicitly
Examples:
import ssl
from fmd_api import FmdClient
# 1) Custom CA bundle / pinned cert (recommended)
ctx = ssl.create_default_context()
ctx.load_verify_locations(cafile="/path/to/your/ca.pem")
# Via constructor
client = FmdClient("https://fmd.example.com", ssl=ctx)
# Or via factory
# async with await FmdClient.create("https://fmd.example.com", "user", "pass", ssl=ctx) as client:
# 2) Disable verification (development only)
insecure_client = FmdClient("https://fmd.example.com", ssl=False)
Notes:
- HTTP (http://) is rejected. Use only HTTPS URLs.
- Prefer a custom SSLContext over disabling verification.
- For higher security, consider pinning the server cert in your context.
Warning
Passing
ssl=Falsedisables TLS certificate validation and should only be used in development. For production, use a customssl.SSLContextthat trusts your CA/certificate or pin the server certificate. The client enforces HTTPS and rejectshttp://URLs.
Pinning the exact server certificate (recommended for self-signed)
If you're using a self-signed certificate and want to pin to that exact cert, load the server's PEM (or DER) directly into an SSLContext. This ensures only that certificate (or its CA) is trusted.
import ssl
from fmd_api import FmdClient
# Export your server's certificate to PEM (e.g., server-cert.pem)
ctx = ssl.create_default_context()
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.check_hostname = True # keep hostname verification when possible
ctx.load_verify_locations(cafile="/path/to/server-cert.pem")
client = FmdClient("https://fmd.example.com", ssl=ctx)
# async with await FmdClient.create("https://fmd.example.com", "user", "pass", ssl=ctx) as client:
Tips:
- If the server cert changes, pinning will fail until you update the PEM.
- For intermediate/CA signing chains, prefer pinning a private CA instead of the leaf.
What’s in the box
-
FmdClient(primary API)-
Auth and key retrieval (salt → Argon2id → access token → private key decrypt)
-
Decrypt blobs (RSA‑OAEP wrapped AES‑GCM)
-
Fetch data:
get_locations,get_pictures -
Export:
export_data_zip(out_path)— client-side packaging of all locations/pictures into ZIP (mimics web UI, no server endpoint) -
Validated command helpers:
request_location("all|gps|cell|last")take_picture("front|back")set_bluetooth(enable: bool)— True = on, False = offset_do_not_disturb(enable: bool)— True = on, False = offset_ringer_mode("normal|vibrate|silent")get_device_stats()
-
Low‑level:
decrypt_data_blob(b64_blob)
-
-
Devicehelper (per‑device convenience)await device.refresh()→ hydrate cached stateawait device.get_location()→ parsed last locationawait device.fetch_pictures(n)+await device.download_photo(item)
Testing
Functional tests
Runnable scripts under tests/functional/:
test_auth.py– basic auth smoke testtest_locations.py– list and decrypt recent locationstest_pictures.py– list and download/decrypt a phototest_device.py– device helper flowstest_commands.py– validated command wrappers (no raw strings)test_export.py– export data to ZIPtest_request_location.py– request location and poll for results
Put credentials in tests/utils/credentials.txt (copy from credentials.txt.example).
Unit tests
Located in tests/unit/:
test_client.py– client HTTP flows with mocked responsestest_device.py– device wrapper logic
Run with pytest:
pip install -e ".[dev]"
pytest tests/unit/
API highlights
- Encryption compatible with FMD web client
- RSA‑3072 OAEP (SHA‑256) wrapping AES‑GCM session key
- AES‑GCM IV: 12 bytes; RSA packet size: 384 bytes
- Password/key derivation with Argon2id
- Robust HTTP JSON/text fallback and 401 re‑auth
Troubleshooting
- "Blob too small for decryption": server returned empty/placeholder data. Skip and continue.
- Pictures may be double‑encoded (encrypted blob → base64 image string). The examples show how to decode safely.
Credits
This client targets the FMD ecosystem:
- https://fmd-foss.org/
- https://gitlab.com/fmd-foss
- Public community instance: https://fmd.nulide.de/
MIT © 2025 Devin Slick
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fmd_api-2.0.2.tar.gz.
File metadata
- Download URL: fmd_api-2.0.2.tar.gz
- Upload date:
- Size: 18.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5002507a0e066371dd1ed7a721a03bf1951235c78842d5fccf61967ae220d4b
|
|
| MD5 |
d29390e2d0e6557ca6bccbb3a4a43b17
|
|
| BLAKE2b-256 |
c7116a2e39db784eef2b6d17f3f67bc9a41d9f4b4012a04511dbb2360c483fbc
|
Provenance
The following attestation bundles were made for fmd_api-2.0.2.tar.gz:
Publisher:
publish.yml on devinslick/fmd_api
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fmd_api-2.0.2.tar.gz -
Subject digest:
f5002507a0e066371dd1ed7a721a03bf1951235c78842d5fccf61967ae220d4b - Sigstore transparency entry: 676699695
- Sigstore integration time:
-
Permalink:
devinslick/fmd_api@2ba88a2d195155f4e17a70ab308463fff6c56927 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/devinslick
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2ba88a2d195155f4e17a70ab308463fff6c56927 -
Trigger Event:
push
-
Statement type:
File details
Details for the file fmd_api-2.0.2-py3-none-any.whl.
File metadata
- Download URL: fmd_api-2.0.2-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
746cfde6eead79e89765b087c8d49a28e5c4b16345ed253fd3de82d9a658c9a6
|
|
| MD5 |
00fcf0ee7e498e9f8a684228c4e904a2
|
|
| BLAKE2b-256 |
271db9ef2fdc24af759fdd4d835e6755aba36d6e75e363f77612e94461774ba7
|
Provenance
The following attestation bundles were made for fmd_api-2.0.2-py3-none-any.whl:
Publisher:
publish.yml on devinslick/fmd_api
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fmd_api-2.0.2-py3-none-any.whl -
Subject digest:
746cfde6eead79e89765b087c8d49a28e5c4b16345ed253fd3de82d9a658c9a6 - Sigstore transparency entry: 676699698
- Sigstore integration time:
-
Permalink:
devinslick/fmd_api@2ba88a2d195155f4e17a70ab308463fff6c56927 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/devinslick
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2ba88a2d195155f4e17a70ab308463fff6c56927 -
Trigger Event:
push
-
Statement type: