Fetching device locations#
Note
The steps below assume that you have already obtained an AppleAccount
instance with a login session attached.
If you don’t have this yet, follow the instructions here to obtain one.
Step 1: Obtaining device information#
In order to fetch location reports for your device, FindMy.py requires the keys that are used to encrypt the location reports that are uploaded by other Apple devices. Depending on the device you are using, this process can differ somewhat.
Tip
This step can be quite annoying, but don’t fret! You only have to do this once for each device you want to track. Can’t figure it out? Join the Discord server and we’ll try to help!
If you want to track an official FindMy device (AirTag, iPhone/iPad/Mac, 3rd party ‘works with FindMy’), you currently need access to a device running MacOS. This can be either a real device or a Hackintosh, however, make sure that you are signed into your Apple account and that the FindMy app is able to track your device. This is a one-time process, so you can also ask a friend to borrow their Mac.
Note that not all versions of MacOS are currently supported. Please see the menus below for more details.
FindMy.py includes a built-in utility that will dump the accessories from your Mac. Note that it will pop up an interactive password prompt to unlock the keychain; therefore, this utility does not work over SSH.
python3 -m findmy decrypt --out-dir devices/
The above command will write one JSON file for each accessory found on your system to the devices
directory.
These files are ready to be used with FindMy.py!
MacOS 15 may or may not include additional protection for the BeaconStoreKey. You should first try to follow the instructions for MacOS 14. If these do not work for you, read on.
If the instructions for MacOS 14 do not work for you, the BeaconStoreKey is likely protected. We will need to use an additional utility to decrypt a set of ‘plist’ files. Go and follow the instructions at @pajowu’s beaconstorekey-extractor, then return here.
Welcome back! Did you remember to re-enable System Integrity Protection? If not, go do that now!
If all went well, you should now have one or multiple decrypted plist files. Hooray! That was the most difficult part. These plist files are not directly compatible with FindMy.py, so we’ll need to convert them first. Save this plist_to_json script somewhere on your computer and run it as follows:
python3 plist_to_json.py path/to/original_file.plist device.json
This will convert a single plist file into a FindMy.py-compatible JSON file and save it to device.json
.
Repeat this step for any other plist files you want to convert.
Note
The first time you try to fetch the location of your device, FindMy.py might appear to hang for a bit. This is because the beaconstorekey-extractor tool does not export key alignment data, so FindMy.py needs to query a wide range of possible values to find the right alignment to use. The older your tag is, the longer it will take to do this process.
If you are physically close to the tag, you can speed this up significantly by using the Tag Scanner. This will attempt to discover your tag via Bluetooth and update its alignment based on the values that it is currently broadcasting. Make sure to give it your device JSON file as argument! Otherwise, the scanner does not know which tag to look for.
MacOS 26 appears to protect the BeaconStoreKey needed to decrypt the plist records that contain accessory data. Unlike with MacOS 15, disabling SIP does not appear to fix it.
If you figure out a way to dump the plist encryption key, please share your findings here.
Unfortunately, FindMy.py currently only supports dumping accessory information from a Mac. Device encryption keys are stored in your account’s keychain, which is only accessible on Apple hardware. iOS / iPadOS is too limited and does not allow us to access the necessary device secrets.
A method to join the encrypted keychain circle from non-MacOS hardware has recently been found, but it takes a lot of time and effort to implement. We are currently considering what the best way would be to implement this, however, we are not currently actively working on making this happen. You can follow development on this feature and voice your support in this GitHub issue.
If you built your own FindMy tag (using e.g. OpenHaystack, macless-haystack, or one of the many other available projects), it will most likely be broadcasting a static key. In this case, grab the private key that you generated and create a KeyPair object as follows:
# PRIVATE key in base64 format
device = KeyPair.from_b64(...)
Don’t have a private key yet?
If you are setting up your DIY tag and have not generated a private key yet, you can use FindMy.py to do it!
device = KeyPair.new()
print(device.private_key_b64)
# a6C9bgy4H/bpZ7vGtVBdO3/UyNjan2/3a7UW4w==
Step 2: Testing your device JSON file (optional)#
At this point, you should be able to fetch location reports for your accessory. FindMy.py includes extensive example scripts to help you test this.
Clone the FindMy.py repository somewhere and enter the examples/
directory.
Then run the following command:
python3 airtag.py path_to_device.json
The script will ask for your account credentials. If all went well, it will output a location report as follows:
Last known location:
- LocationReport(hashed_adv_key=..., timestamp=..., lat=..., lon=...)
Clone the FindMy.py repository somewhere and enter the examples/
directory.
Then run the following command:
python3 fetch_reports.py <private_key_base64>
The script will ask for your account credentials. If all went well, it will output a location report as follows:
Last known location:
- LocationReport(hashed_adv_key=..., timestamp=..., lat=..., lon=...)
Step 3: Fetching location reports#
To fetch location report for a device, you can use the fetch_location method
on your AppleAccount instance. This method will return either a LocationReport
if a location is found, or None
if no location was found.
location = account.fetch_location(device)
print(location)
# LocationReport(...)
If you want to query locations for multiple devices, you can also pass in a list. FindMy.py will then optimize its request payloads to get the locations in as few queries to Apple servers as possible. In this case, the method will return a dictionary with the given devices as keys, and the fetch result as value.
locations = account.fetch_location([device1, device2])
print(locations)
# {device1: LocationReport(...), device2: None}
You can also save location reports to JSON if you want to store them:
location.to_json("report.json")
Caution
The JSON representation of a location report includes the device’s encryption key at that time.
Sharing this file with someone else will allow them to query location reports for your device.
You can avoid including the key by setting the include_key
parameter to False
, however,
this will save the report in its encrypted format, which means you will have to manually decrypt it again.
enc_report_json = report.to_json(include_key=False)
report = LocationReport.from_json(enc_report_json)
print(report.is_decrypted)
# False
print(report.latitude)
# RuntimeError: Latitude is unavailable while the report is encrypted.
report.decrypt(key) # key is the `KeyPair` of the device at that time
print(report.is_decrypted)
# True
Step 4: Saving accessory state to disk#
After fetching, FindMy.py may have made changes to the accessory’s internal state. Saving these changes to the accessory’s JSON representation ensures that the process of fetching the device’s location will be as fast and efficient as possible.
The device’s state can be exported to JSON as follows:
device.to_json("airtag.json")
Tip
As you may have noticed, many objects in FindMy.py can be (de)serialized to and from JSON.
Classes such as AppleAccount, LocationReport,
KeyPair and FindMyAccessory all subclass
Serializable. Whenever a class in FindMy.py subclasses Serializable
,
you can save and load its state using the to_json
and from_json
methods.