# Airpinpoint API — Complete Reference > REST API for tracking AirTags and Find My compatible devices. Real-time locations, location history, geofence alerts, and webhook notifications. ## Instructions for AI Agents - Base URL: https://api.airpinpoint.com/v1 - Auth: API key as `Authorization: Bearer YOUR_API_KEY` or `X-API-Key: YOUR_API_KEY` (both equivalent) - Rate limit: 100 req/min per API key. 429 on exceed. - All timestamps: ISO 8601 UTC (e.g. 2024-01-15T10:30:00Z) - Pagination: `skip` (default 0) and `limit` on all list endpoints - Errors return: `{"error": {"code": "...", "message": "..."}}` - Status codes: 200 success, 400 bad request, 401 unauthorized, 404 not found, 429 rate limited, 500 server error --- ## Authentication Generate an API key from your dashboard at https://airpinpoint.com/dashboard/settings. ```bash # Option 1: Bearer token (recommended) curl -X GET "https://api.airpinpoint.com/v1/trackables" \ -H "Authorization: Bearer YOUR_API_KEY" # Option 2: X-API-Key header curl -X GET "https://api.airpinpoint.com/v1/trackables" \ -H "X-API-Key: YOUR_API_KEY" ``` --- ## Trackables Trackables are your AirTags and Find My devices. ### List all trackables `GET /v1/trackables` Query params: - `skip`: int (default 0, min 0) — pagination offset - `limit`: int (default 20, min 1, max 100) — results per page Response: array of TrackableDetail objects (see schema below). ```bash curl -X GET "https://api.airpinpoint.com/v1/trackables?limit=20" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Get a single trackable `GET /v1/trackables/{trackable_id}` Returns a TrackableDetail with lastKnownLocation and batteryInfo populated. ### TrackableDetail schema ```json { "id": "string", "model": "string | null (e.g. AirTag, iPhone)", "name": "string | null (name from Find My)", "enabled": true, "createdAt": "2024-01-10T09:15:22Z", "pairedAt": "2024-01-10T09:15:22Z | null", "lastKnownLocation": { "id": "string", "trackableId": "string", "name": "string | null", "latitude": 37.7749, "longitude": -122.4194, "altitude": "number | null", "horizontalAccuracy": 10.0, "verticalAccuracy": "number | null", "batteryLevel": "number | null", "timestamp": "2024-01-15T14:30:00Z", "isOld": "boolean | null", "isInaccurate": "boolean | null", "createdAt": "datetime | null", "updatedAt": "datetime | null", "deletedAt": "datetime | null" }, "batteryInfo": { "lastBatteryReset": "2024-01-01T00:00:00Z | null", "batteryMonths": 12, "estimatedDaysRemaining": 200, "batteryPercentage": 75.5 } } ``` ### Get battery info `GET /v1/trackables/{trackable_id}/battery` Returns BatteryInfo object. Battery percentage is calculated from lastBatteryReset and batteryMonths (approximated as batteryMonths * 30 days). ### Reset battery (after replacement) `POST /v1/trackables/{trackable_id}/battery` Body: ```json {"batteryMonths": 12} ``` Sets lastBatteryReset to now and batteryMonths to the provided value. --- ## Locations ### Get current location `GET /v1/trackables/{trackable_id}/current_location` Returns the single most recent Location for the device. 404 if no location data or device not enabled. ### Get location history `GET /v1/trackables/{trackable_id}/locations` Query params: - `skip`: int (default 0, min 0) - `limit`: int (default 10, min 1, max 1000) - `start_time`: ISO 8601 datetime (default: 7 days ago) - `end_time`: ISO 8601 datetime (default: now) Returns array of Location objects ordered by timestamp descending. 404 if no data found. ```bash curl -X GET "https://api.airpinpoint.com/v1/trackables/trk_xyz/locations?start_time=2024-01-14T00:00:00Z&end_time=2024-01-15T23:59:59Z&limit=100" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Location schema ```json { "id": "string", "trackableId": "string", "name": "string | null (device name)", "latitude": 37.7749, "longitude": -122.4194, "altitude": "number | null", "horizontalAccuracy": 10.0, "verticalAccuracy": "number | null", "batteryLevel": "number | null", "timestamp": "2024-01-15T10:30:00Z", "isOld": "boolean | null", "isInaccurate": "boolean | null", "createdAt": "datetime | null", "updatedAt": "datetime | null", "deletedAt": "datetime | null" } ``` Lower horizontalAccuracy values = more precise. Under 20m is reliable. --- ## Geofences Virtual boundaries that trigger notifications when devices enter or exit. ### List geofences `GET /v1/geofences` Query params: - `skip`: int (default 0) - `limit`: int (default 20, max 100) - `trackable_id`: string (optional, filter geofences for a specific device) ### Create geofence `POST /v1/geofences` Body: ```json { "name": "Warehouse Zone A", "latitude": 37.7749, "longitude": -122.4194, "radius": 100, "trackableId": ["trk_123", "trk_456"], "notifyType": "both", "notifyDestination": "https://yourapp.com/webhook", "webhookSecret": "your_secret_key", "webhookEnabled": true, "webhookHeaders": {"X-Custom": "value"} } ``` Fields: - `name`: string (required) - `latitude`: float (required) - `longitude`: float (required) - `radius`: float (required, 10-5000 meters) - `trackableId`: string[] (required, array of device IDs) - `notifyType`: "enter" | "exit" | "both" | "webhook" (optional) - `notifyDestination`: email or webhook URL (optional) - `webhookSecret`: string for HMAC signing (optional) - `webhookEnabled`: boolean (optional, default false) - `webhookHeaders`: object with custom HTTP headers (optional) Limit: 10 geofences per account. ### Update geofence `PATCH /v1/geofences/{geofence_id}` Body: same fields as create, all optional. Only provided fields are updated. ### Delete geofence `DELETE /v1/geofences/{geofence_id}` Returns: `{"message": "Geofence deleted successfully"}` ### Geofence schema ```json { "id": "string", "name": "string", "latitude": 37.7749, "longitude": -122.4194, "radius": 100.0, "notifyType": "string | null", "notifyDestination": "string | null", "trackableId": ["trk_123", "trk_456"], "webhookEnabled": false, "lastWebhookSuccess": "datetime | null" } ``` --- ## Webhooks Webhooks send HTTP POST to your endpoint when devices enter/exit geofences. ### Webhook payload ```json { "event": "geofence.entry", "occurred_at": "2024-01-15T17:30:00Z", "geofence": { "id": "geofence_789", "name": "Warehouse Zone A", "latitude": 37.7749, "longitude": -122.4194, "radius": 100 }, "beacon": { "id": "trk_123", "name": "Forklift #3", "type": "apple" }, "location": { "latitude": 37.7752, "longitude": -122.4192, "accuracy": 10, "timestamp": "2024-01-15T17:30:00Z" } } ``` Event types: `geofence.entry`, `geofence.exit` ### Webhook signature verification When webhookSecret is set, the X-Airpinpoint-Signature header contains HMAC-SHA256 of the JSON payload. ```javascript const crypto = require('crypto'); function verifyWebhookSignature(payload, signature, secret) { const computed = crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(computed) ); } app.post('/webhooks/geofence', (req, res) => { const sig = req.headers['x-airpinpoint-signature']; if (!verifyWebhookSignature(req.body, sig, 'your_secret')) { return res.status(403).send('Invalid signature'); } // Process event... res.status(200).send('OK'); }); ``` ### Webhook retries Failed deliveries (non-2xx) are retried 3 times: - 1st retry: 5 seconds after failure - 2nd retry: 30 seconds after 1st retry - 3rd retry: 120 seconds after 2nd retry Timeout: 10 seconds per attempt. ### Test a webhook `POST /v1/geofences/{geofence_id}/test-webhook` Body: ```json {"eventType": "entry"} ``` Sends a test payload to your configured endpoint. Returns: ```json {"success": true, "message": "Test webhook successfully sent for entry event", "deliveryId": "..."} ``` ### Get webhook delivery history `GET /v1/geofences/{geofence_id}/webhooks` Query params: - `limit`: int (default 10, max 100) - `successful`: boolean (optional filter) ### Get webhook delivery detail `GET /v1/geofences/{geofence_id}/webhooks/{delivery_id}` Returns full delivery info including payload and response body. --- ## Share Links Generate temporary public URLs to share a device's location. ### Create share link `POST /v1/share-links` Body: ```json { "trackableId": "trk_123", "hours": 24 } ``` - `trackableId`: string (required) - `hours`: int 1-168 (required, how long the link is valid) Response: ```json {"shareUrl": "https://airpinpoint.com/share/8a7b6c5d-4e3f-2d1c-0b9a-..."} ``` --- ## Account ### Get account info `GET /v1/account` Returns the authenticated user's info: ```json { "id": "uuid", "name": "string | null", "email": "string | null", "emailVerified": "datetime | null", "image": "string | null" } ``` --- ## Usage ### Get usage history `GET /v1/usage` Query params: - `start_date`: datetime (default: 30 days ago) - `end_date`: datetime (default: now) ### Get total request count `GET /v1/usage/total` Same params. Returns integer count. --- ## Plans & Pricing | Plan | Price | Refresh Rate | Key Features | |------|-------|-------------|--------------| | Business | $11.99/tag/mo | 1 min | Location history, fleet view, geofencing, SMS/email alerts, analytics | | Enterprise | $14.99/tag/mo | 1 min | All Business + webhooks, multi-user RBAC, reports, CSV export | Purchase AirTags directly from https://airpinpoint.com/dashboard/billing. Pre-configured tags recommended. Self-service (bring your own AirTags) requires a Mac for one-time setup. Limits: - Max 32 AirTags per iCloud account - Max 3 iCloud accounts per iPhone/iPad