npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@airpinpoint/sdk

v1.0.0

Published

Official TypeScript SDK for the AirPinpoint asset tracking API

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 airpinpoint

Quick 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 tolerance

Filtering

// 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