Official Python SDK for the AirPinpoint asset tracking API
Project description
AirPinpoint Python SDK
Official Python SDK for the AirPinpoint asset tracking API. Track Apple AirTags and Find My compatible devices with type-safe, production-grade Python.
Installation
pip install airpinpoint
Requires Python 3.9+.
Quick Start
from airpinpoint import AirPinpoint
# Reads AIRPINPOINT_API_KEY from environment, or pass it directly
client = AirPinpoint(api_key="your-api-key")
# List all trackables
page = client.trackables.list()
for trackable in page.data:
print(f"{trackable.name}: {trackable.enabled}")
Authentication
Get your API key from the AirPinpoint Dashboard under API Keys.
# Option 1: Pass directly
client = AirPinpoint(api_key="your-api-key")
# Option 2: Environment variable
# export AIRPINPOINT_API_KEY=your-api-key
client = AirPinpoint()
Usage
Trackables
# List all trackables
page = client.trackables.list()
# Get a single trackable
trackable = client.trackables.retrieve("trackable-id")
# Get current location
location = client.trackables.current_location("trackable-id")
print(f"Lat: {location.latitude}, Lng: {location.longitude}")
# Get location history
from datetime import datetime
history = client.trackables.location_history(
"trackable-id",
start_time=datetime(2026, 1, 1),
end_time=datetime(2026, 4, 1),
limit=100,
)
for loc in history.data:
print(f"{loc.timestamp}: ({loc.latitude}, {loc.longitude})")
# Battery info
battery = client.trackables.battery("trackable-id")
print(f"Battery: {battery.battery_percentage}%")
# Reset battery tracking
client.trackables.reset_battery("trackable-id", battery_months=12)
Auto-Pagination
All list methods return paginated results. Iterate directly to auto-paginate:
# Auto-paginate through all trackables
for trackable in client.trackables.list():
print(trackable.name)
# Or access the first page manually
page = client.trackables.list(limit=10)
print(page.data) # list of items
print(page.has_more) # True if more pages exist
Geofences
# List geofences
geofences = client.geofences.list()
# Create a geofence
geofence = client.geofences.create(
name="Office",
latitude=37.7749,
longitude=-122.4194,
radius=500,
trackable_id=["trackable-id-1", "trackable-id-2"],
webhook_enabled=True,
webhook_secret="your-secret",
)
# Update (only sends fields you provide)
from airpinpoint import NOT_GIVEN
updated = client.geofences.update(
"geofence-id",
name="New Office",
radius=1000,
)
# Delete
client.geofences.delete("geofence-id")
# Test webhook
result = client.geofences.test_webhook("geofence-id", event_type="entry")
print(result.success)
# List webhook deliveries
deliveries = client.geofences.webhooks.list("geofence-id")
for d in deliveries.data:
print(f"{d.event_type}: {'OK' if d.successful else 'FAILED'}")
# Get delivery detail
detail = client.geofences.webhooks.retrieve("geofence-id", "delivery-id")
print(detail.payload)
Share Links
link = client.share_links.create(trackable_id="trackable-id", hours=24)
print(link.share_url)
Account & Usage
user = client.account.retrieve()
print(f"Logged in as: {user.email}")
usage = client.usage.retrieve(start_date="2026-01-01", end_date="2026-04-01")
total = client.usage.total()
Async Client
Every method has an async equivalent:
import asyncio
from airpinpoint import AsyncAirPinpoint
async def main():
async with AsyncAirPinpoint(api_key="your-api-key") as client:
# All the same methods, just awaited
trackables = await client.trackables.list()
for t in trackables.data:
print(t.name)
# Async auto-pagination
async for trackable in await client.trackables.list():
print(trackable.name)
location = await client.trackables.current_location("trackable-id")
print(f"{location.latitude}, {location.longitude}")
asyncio.run(main())
Webhook Verification
Verify incoming webhooks from AirPinpoint:
from airpinpoint import Webhook, WebhookSignatureError
try:
event = Webhook.construct_event(
payload=request.body, # raw request body
signature=request.headers["X-AirPinpoint-Signature"],
secret="your-webhook-secret",
)
print(f"Event: {event.event}") # "geofence.entry" or "geofence.exit"
print(f"Beacon: {event.beacon}")
print(f"Location: {event.location}")
except WebhookSignatureError as e:
print(f"Invalid signature: {e}")
Utility Functions
Location-aware helper functions for distance calculation, path analysis, geofencing, and data formatting.
from airpinpoint import utils
# Distance and bearing between two locations
d = utils.distance(location_a, location_b) # meters
b = utils.bearing(location_a, location_b) # degrees [0, 360)
# Geofence checks
inside = utils.is_inside_geofence(location, geofence)
dist = utils.distance_to_geofence(location, geofence)
# Polygon containment (ray-casting)
inside = utils.is_inside_polygon(location, polygon_vertices)
# Speed between timestamped locations
s = utils.speed(loc1, loc2) # m/s
# Path analysis
stops = utils.detect_stops(locations, radius_m=50, min_duration_ms=300_000)
trips = utils.detect_trips(locations)
dwell = utils.dwell_time(locations, geofence) # milliseconds
simplified = utils.simplify_path(locations, tolerance_m=10)
clusters = utils.cluster_locations(locations, radius_m=100)
# GeoJSON export
geojson = utils.to_geojson(locations) # FeatureCollection
point = utils.to_geojson_point(location) # Feature<Point>
line = utils.to_geojson_line_string(locations) # Feature<LineString>
# Google Encoded Polyline
encoded = utils.encode_polyline(locations)
decoded = utils.decode_polyline(encoded) # list of (lat, lng)
# Filter noisy data
clean = utils.filter_by_accuracy(locations, max_accuracy_m=100)
clean = utils.filter_by_speed(locations, max_speed_mps=100)
clean = utils.filter_stale(locations, max_age_ms=3_600_000)
clean = utils.deduplicate(locations, distance_threshold_m=1, time_threshold_ms=1000)
# Battery estimation
days = utils.estimate_battery_days_remaining(battery_info)
status = utils.battery_health_status(battery_info) # "good" | "low" | "critical" | "replace"
All utility functions accept duck-typed objects. Anything with .latitude and .longitude attributes works, including SDK types like LocationBase and GeofenceBase.
Error Handling
from airpinpoint import (
AirPinpoint,
AirPinpointError,
AuthenticationError,
NotFoundError,
RateLimitError,
ValidationError,
)
client = AirPinpoint(api_key="your-api-key")
try:
trackable = client.trackables.retrieve("invalid-id")
except NotFoundError as e:
print(f"Not found: {e.message}")
except AuthenticationError:
print("Invalid API key")
except RateLimitError:
print("Too many requests, slow down")
except ValidationError as e:
print(f"Bad request: {e.message}")
except AirPinpointError as e:
print(f"SDK error: {e}")
Error hierarchy:
AirPinpointError
APIError (status_code, message, body, headers)
AuthenticationError (401)
NotFoundError (404)
ValidationError (400)
RateLimitError (429)
InternalError (500+)
APIConnectionError
WebhookSignatureError
Configuration
import httpx
client = AirPinpoint(
api_key="your-api-key",
base_url="https://api.airpinpoint.com/v1", # default
timeout=httpx.Timeout(connect=5, read=30, write=30, pool=30),
max_retries=2, # retries on 429/5xx
default_headers={"X-Custom": "value"},
)
Context Manager
Both clients support context managers for clean resource management:
# Sync
with AirPinpoint(api_key="...") as client:
trackables = client.trackables.list()
# Async
async with AsyncAirPinpoint(api_key="...") as client:
trackables = await client.trackables.list()
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 airpinpoint-0.1.0.tar.gz.
File metadata
- Download URL: airpinpoint-0.1.0.tar.gz
- Upload date:
- Size: 20.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83e10256027f77e664563766e7b285e8f5cecb55587c1db8862f5f7fb997f03b
|
|
| MD5 |
484a9badf4f411924e049bf004121568
|
|
| BLAKE2b-256 |
9ef46095016439a3eef4f2125e359b2c0459239905c7a55019e94f8bd4dd58b3
|
Provenance
The following attestation bundles were made for airpinpoint-0.1.0.tar.gz:
Publisher:
publish-python-sdk.yml on bhaktatejas922/airtag-api
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
airpinpoint-0.1.0.tar.gz -
Subject digest:
83e10256027f77e664563766e7b285e8f5cecb55587c1db8862f5f7fb997f03b - Sigstore transparency entry: 1278100613
- Sigstore integration time:
-
Permalink:
bhaktatejas922/airtag-api@a86ce4210f11d1d1bd1c3c003b57084f21140396 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/bhaktatejas922
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python-sdk.yml@a86ce4210f11d1d1bd1c3c003b57084f21140396 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file airpinpoint-0.1.0-py3-none-any.whl.
File metadata
- Download URL: airpinpoint-0.1.0-py3-none-any.whl
- Upload date:
- Size: 27.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d335c617cf1deccb8c3d06da485051ca39d8f29ee006bae0bdbbcd68bb36bf7b
|
|
| MD5 |
580bb2c177e371dfae403eddf8c34fb0
|
|
| BLAKE2b-256 |
832cc52b75e42897185157046638e43f75b4edf7cef03425de18c848dd787604
|
Provenance
The following attestation bundles were made for airpinpoint-0.1.0-py3-none-any.whl:
Publisher:
publish-python-sdk.yml on bhaktatejas922/airtag-api
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
airpinpoint-0.1.0-py3-none-any.whl -
Subject digest:
d335c617cf1deccb8c3d06da485051ca39d8f29ee006bae0bdbbcd68bb36bf7b - Sigstore transparency entry: 1278100618
- Sigstore integration time:
-
Permalink:
bhaktatejas922/airtag-api@a86ce4210f11d1d1bd1c3c003b57084f21140396 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/bhaktatejas922
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python-sdk.yml@a86ce4210f11d1d1bd1c3c003b57084f21140396 -
Trigger Event:
workflow_dispatch
-
Statement type: