@airpinpoint/sdk
v1.0.0
Published
Official TypeScript SDK for the AirPinpoint asset tracking API
Maintainers
Readme
AirPinpoint TypeScript SDK
Official TypeScript/JavaScript SDK for the AirPinpoint asset tracking API. Track AirTags and Find My compatible devices, manage geofences, receive webhook alerts, and analyze location data.
- Zero runtime dependencies
- ESM + CJS dual build
- Node 18+, Deno, Bun, edge runtimes
- Full TypeScript types
- Auto-pagination
- Retry with exponential backoff
- Webhook signature verification
Installation
npm install airpinpoint
# or
bun add airpinpoint
# or
pnpm add airpinpointQuick Start
import { AirPinpoint } from "airpinpoint";
const ap = new AirPinpoint({ apiKey: "your-api-key" });
// Or set AIRPINPOINT_API_KEY env var and call new AirPinpoint()
// Get account info
const account = await ap.account.retrieve();
// List all trackables
const { data: trackables } = await ap.trackables.list();
// Get current location of a specific device
const location = await ap.trackables.currentLocation("beacon-id");
console.log(`${location.latitude}, ${location.longitude}`);Trackables
// List trackables (first page)
const { data } = await ap.trackables.list({ limit: 50 });
// Auto-paginate through all trackables
for await (const trackable of ap.trackables.list()) {
console.log(trackable.name, trackable.enabled);
}
// Get a single trackable with last known location
const device = await ap.trackables.retrieve("beacon-id");
console.log(device.lastKnownLocation?.latitude);
// Get current location
const loc = await ap.trackables.currentLocation("beacon-id");
// Get location history with time range
const history = await ap.trackables.locationHistory("beacon-id", {
startTime: "2026-01-01T00:00:00Z",
endTime: "2026-04-01T00:00:00Z",
limit: 1000,
});
// Battery info
const battery = await ap.trackables.battery("beacon-id");
console.log(`${battery.estimatedDaysRemaining} days remaining`);
// Reset battery tracking after replacement
await ap.trackables.resetBattery("beacon-id", { batteryMonths: 12 });Geofences
// Create a geofence
const fence = await ap.geofences.create({
name: "Warehouse",
latitude: 37.7749,
longitude: -122.4194,
radius: 500,
trackableId: ["beacon-1", "beacon-2"],
webhookEnabled: true,
});
// List geofences
const { data: fences } = await ap.geofences.list();
// Filter by trackable
const { data: filtered } = await ap.geofences.list({
trackableId: "beacon-1",
});
// Update a geofence
await ap.geofences.update(fence.id, { radius: 1000 });
// Delete a geofence
await ap.geofences.del(fence.id);
// Test webhook delivery
const result = await ap.geofences.testWebhook(fence.id, {
eventType: "entry",
});
// List webhook deliveries
const deliveries = await ap.geofences.webhooks.list(fence.id);
// Get delivery details
const detail = await ap.geofences.webhooks.retrieve(fence.id, deliveries[0].id);Share Links
const { shareUrl } = await ap.shareLinks.create({
trackableId: "beacon-id",
hours: 24,
});
console.log(shareUrl);Usage
const stats = await ap.usage.retrieve({
startDate: "2026-01-01",
endDate: "2026-04-01",
});
const total = await ap.usage.total();Auto-Pagination
Every list method returns a PaginatedList that works both as a promise (first page) and as an async iterator (all pages):
// First page only
const { data } = await ap.trackables.list({ limit: 10 });
// Iterate all pages automatically
for await (const trackable of ap.trackables.list({ limit: 50 })) {
process.stdout.write(".");
}Webhook Verification
Verify incoming webhook signatures using the Web Crypto API (works in Node, Deno, Bun, and edge runtimes):
import { webhooks } from "airpinpoint/webhooks";
// In your webhook handler
const event = await webhooks.constructEvent(
rawBody, // raw request body string
request.headers["x-airpinpoint-signature"],
"your-webhook-secret",
);
switch (event.event) {
case "geofence.entry":
console.log(`${event.beacon.name} entered ${event.geofence.name}`);
break;
case "geofence.exit":
console.log(`${event.beacon.name} exited ${event.geofence.name}`);
break;
}Utility Functions
Location-specific utilities for analysis, filtering, and format conversion. Zero dependencies, pure functions.
import {
distance,
bearing,
isInsideGeofence,
detectStops,
detectTrips,
simplifyPath,
toGeoJSON,
encodePolyline,
filterByAccuracy,
filterBySpeed,
deduplicate,
batteryHealthStatus,
} from "airpinpoint/utils";Geo
// Distance between two points (meters)
const meters = distance(
{ latitude: 37.7749, longitude: -122.4194 },
{ latitude: 34.0522, longitude: -118.2437 },
);
// Bearing (degrees)
const deg = bearing(pointA, pointB);
// Point-in-geofence check
const inside = isInsideGeofence(
{ latitude: 37.775, longitude: -122.419 },
geofence, // any object with { latitude, longitude, radius }
);Analysis
// Detect stops (dwell clusters)
const stops = detectStops(locations, {
radiusM: 50,
minDurationMs: 300_000, // 5 minutes
});
// Detect trips between stops
const trips = detectTrips(locations);
// Time spent inside a geofence
const ms = dwellTime(locations, geofence);
// Simplify path (Douglas-Peucker)
const simplified = simplifyPath(locations, 10); // 10m toleranceFiltering
// Remove inaccurate readings
const accurate = filterByAccuracy(locations, 50); // max 50m accuracy
// Remove GPS teleport jumps
const clean = filterBySpeed(locations, 100); // max 100 m/s
// Remove stale readings
const fresh = filterStale(locations, 3600_000); // max 1 hour old
// Remove near-duplicates
const unique = deduplicate(locations, { radiusM: 5, timeWindowMs: 60_000 });Format Conversion
// GeoJSON
const collection = toGeoJSON(locations); // FeatureCollection<Point>
const line = toGeoJSONLineString(locations); // Feature<LineString>
// Google Encoded Polyline
const encoded = encodePolyline(locations);
const decoded = decodePolyline(encoded);Battery
const status = batteryHealthStatus(device.batteryInfo);
// "good" | "low" | "critical" | "replace"Error Handling
import {
APIError,
AuthenticationError,
RateLimitError,
NotFoundError,
ValidationError,
APIConnectionError,
} from "airpinpoint";
try {
await ap.trackables.retrieve("nonexistent");
} catch (err) {
if (err instanceof NotFoundError) {
console.log("Device not found");
} else if (err instanceof RateLimitError) {
console.log("Rate limited, retry later");
} else if (err instanceof AuthenticationError) {
console.log("Invalid API key");
} else if (err instanceof APIConnectionError) {
console.log("Network error:", err.message);
}
}Configuration
const ap = new AirPinpoint({
apiKey: "...", // or AIRPINPOINT_API_KEY env var
baseURL: "https://api.airpinpoint.com/v1", // default
timeout: 30_000, // 30s default
maxRetries: 2, // retries on 429/5xx
fetch: customFetch, // custom fetch implementation
});License
MIT
