Skip to main content

A Python client for the FMD (Find My Device) server API

Project description

fmd_api: Python client for FMD (Find My Device)

Tests codecov

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=False disables TLS certificate validation and should only be used in development. For production, use a custom ssl.SSLContext that trusts your CA/certificate or pin the server certificate. The client enforces HTTPS and rejects http:// 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 = off
      • set_do_not_disturb(enable: bool) — True = on, False = off
      • set_ringer_mode("normal|vibrate|silent")
      • get_device_stats()
    • Low‑level: decrypt_data_blob(b64_blob)

  • Device helper (per‑device convenience)

    • await device.refresh() → hydrate cached state
    • await device.get_location() → parsed last location
    • await device.fetch_pictures(n) + await device.download_photo(item)

Testing

Functional tests

Runnable scripts under tests/functional/:

  • test_auth.py – basic auth smoke test
  • test_locations.py – list and decrypt recent locations
  • test_pictures.py – list and download/decrypt a photo
  • test_device.py – device helper flows
  • test_commands.py – validated command wrappers (no raw strings)
  • test_export.py – export data to ZIP
  • test_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 responses
  • test_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:

MIT © 2025 Devin Slick

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fmd_api-2.0.3.tar.gz (18.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fmd_api-2.0.3-py3-none-any.whl (16.6 kB view details)

Uploaded Python 3

File details

Details for the file fmd_api-2.0.3.tar.gz.

File metadata

  • Download URL: fmd_api-2.0.3.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

Hashes for fmd_api-2.0.3.tar.gz
Algorithm Hash digest
SHA256 5cc9a367d6d221ac9b2944a445f032b22a1e483bcc523b5b5d8f95d6a1a7fc3e
MD5 92230e81b87ed237a1239593576c7297
BLAKE2b-256 c12b90a62b3ffbba038e38b8d0bebba2bc02330cb04889d63fa468beae118af6

See more details on using hashes here.

Provenance

The following attestation bundles were made for fmd_api-2.0.3.tar.gz:

Publisher: publish.yml on devinslick/fmd_api

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fmd_api-2.0.3-py3-none-any.whl.

File metadata

  • Download URL: fmd_api-2.0.3-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

Hashes for fmd_api-2.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 8d3ca81748e6537b1d895bcc81e74d6cbc7a1daabd69bba2d1fd6e6dcfefc4ab
MD5 a6226031e295fd999994e7f48b7f944b
BLAKE2b-256 3aaf1446fd7ac569bb288b1d5c64a1d3c0aab75e982e1beea917a7c295b80551

See more details on using hashes here.

Provenance

The following attestation bundles were made for fmd_api-2.0.3-py3-none-any.whl:

Publisher: publish.yml on devinslick/fmd_api

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page