@suverselabs/pingpoint-sdk
TypeScript SDK for live truck GPS tracking over the PingPoint Agent API: create a freight load, PingPoint texts the driver an app link (onboarding takes about a minute — no ELD provider, no corporate account, no annual contract), then read live position, GPS track, stop timeline and ETA, and cancel a load you no longer need — from your own software or AI agent.
No account yet? A sandbox key is published at https://pingpoint.suverse.io/docs/#sandbox: same endpoints, no signup, and the load drives itself through the full cycle in about 20 minutes.
npm install @suverselabs/pingpoint-sdk
Quick start
import { PingPointAgent } from "@suverselabs/pingpoint-sdk";
const pp = new PingPointAgent({ apiKey: process.env.PINGPOINT_AGENT_KEY! });
const load = await pp.createLoad({
driverPhone: "+15551234567",
pickups: [{ facilityName: "General Mills DC", address: "6492 Tower Lane", city: "Claremore", state: "OK", zip: "74017" }],
deliveries: [{ address: "6499 Caldwell Park Dr", city: "Charlotte", state: "NC", zip: "28269" }],
});
console.log(load.loadNumber, load.trackingLink);
const pos = await pp.getPosition(load.loadNumber);
console.log(pos.status, `${pos.pingCount} GPS points`);
Required on create: driverPhone (E.164) and the pickups / deliveries arrays — each stop needs address, city, state, zip. Everything else (shipperName, carrierName, equipmentType, customerRef, rate, miles, weight, truckNumber) is optional. A load may have several pickups and deliveries. customerRef doubles as a dedup key — re-sending it returns the existing load (deduplicated: true) instead of creating a duplicate.
Operations
createLoad(input, {idempotencyKey?}) | Creates the load; driver gets the app link by SMS | loadNumber, public trackingLink, driver web/app links, dedup flag | $0.65 |
getPosition(loadNumber) | Live state of the load | status, driver tracking state (driverTracking) with invite/open/decline timestamps, GPS track (last 500 points), stops with arrive/depart timestamps, distance, on-time flag, dwell times, ETA block | $0.02 |
getTripStats(loadNumber) | Post-trip summary over the whole GPS trip (best on a DELIVERED load) | stats with distance, duration, avg/max speed, hard accel/brake counts, city/highway/parked/night shares, GPS coverage, first/last ping | $0.02 |
cancelLoad(loadNumber) | Cancels the load: status CANCELLED, tracking stops, nothing deleted, no refund | { ok, loadNumber, previousStatus, status, cancelledAt, trackingEndedAt }; a repeat call adds idempotent: true | free |
updateStatus(loadNumber, status) | Not part of the API — statuses are GPS-verified, see below | throws ApiError (HTTP 501, body.code: "OPERATION_NOT_AVAILABLE") | — |
getPricing() | Current USD price list | { currency, prices } | free |
getBalance() | Prepaid balance | { currency, balanceUsd } | free |
Why updateStatus answers 501. PingPoint drives load statuses itself, from driver GPS and geofence events, so external status writes don't exist. Treat that as a data-integrity guarantee: a status you read was never hand-set by anyone; it is backed by actually recorded position. The method is kept only so a caller that tries it gets a typed error instead of a mystery. Delivery confirmation is likewise not a vendor operation — it belongs to the carrier flow, where the carrier files the BOL over Telegram.
Stops. A load carries 1–2 pickups and 1–3 deliveries; array order is the stop sequence. Each stop takes an optional window: date (start) and dateTo (end). The end matters — onTime and delayMinutes are measured against the dateTo of the last delivery, so without it both stay null. Stops are fixed at creation: there is no add-stop call, and a load cannot be edited afterwards.
Example — read a position
const pos = await pp.getPosition("LD-2026-042317");
{
"loadNumber": "LD-2026-042317",
"status": "IN_TRANSIT",
"createdAt": "2026-08-18T14:02:11.000Z",
"deliveredAt": null,
"driverTracking": "REPORTING",
"inviteSentAt": "2026-08-18T14:02:12.000Z",
"linkOpenedAt": null,
"declinedAt": null,
"distanceMiles": 412.7,
"onTime": null,
"delayMinutes": null,
"pickupDwellMinutes": 38,
"deliveryDwellMinutes": null,
"stops": [
{ "type": "PICKUP", "sequence": 1, "city": "Claremore", "state": "OK",
"windowFrom": "2026-08-18T15:00:00.000Z", "windowTo": "2026-08-18T19:00:00.000Z",
"arrivedAt": "2026-08-18T15:47:31.000Z", "departedAt": "2026-08-18T16:25:09.000Z" },
{ "type": "DELIVERY", "sequence": 2, "city": "Charlotte", "state": "NC",
"windowFrom": "2026-08-20T12:00:00.000Z", "windowTo": "2026-08-20T16:00:00.000Z",
"arrivedAt": null, "departedAt": null }
],
"gpsTrack": [
{ "lat": 36.3126, "lng": -95.6161, "speed": null, "heading": null, "ts": "2026-08-18T16:25:09.000Z" }
],
"pingCount": 214,
"eta": {
"nextStop": { "type": "DELIVERY", "sequence": 2, "city": "Charlotte", "state": "NC" },
"receivingWindow": { "from": "2026-08-20T12:00:00.000Z", "to": "2026-08-20T16:00:00.000Z" },
"distanceToNextStopMi": 611.4,
"driveTimeHours": 11.2,
"moving": true,
"stoppedForMinutes": null,
"etaWindow": { "from": "2026-08-20T13:05:00.000Z", "to": "2026-08-20T15:40:00.000Z" },
"tracking": { "state": "pinging", "noDataForMinutes": null },
"reason": null
}
}
(gpsTrack shortened; the API returns up to the last 500 points.)
Example — trip summary stats
Once the load is DELIVERED (works mid-trip too — returns the trip so far):
const trip = await pp.getTripStats("LD-2026-648319");
{
"loadNumber": "LD-2026-648319",
"loadId": "927741bc-dfd0-41ba-9e99-5c5031c756f9",
"stats": {
"dataPoints": 4785,
"durationSeconds": 69329,
"estimatedDistanceMiles": 486.591,
"avgSpeedMph": 25.27,
"maxSpeedMph": 88.22,
"hardAccelCount": 280,
"hardBrakeCount": 168,
"cityMilesPct": 9.96,
"highwayMilesPct": 88.65,
"parkedTimePct": 53.65,
"nightPct": 44.7,
"coveragePct": 100,
"firstAt": "2026-08-18T16:53:25.000Z",
"lastAt": "2026-08-19T12:08:54.000Z"
}
}
Distances are miles, speeds are mph, percentages are 0–100, timestamps are UTC. getPosition answers "where is the truck now"; getTripStats answers "how did the trip go".
Example — cancel a load
const res = await pp.cancelLoad("LD-2026-815401");
{
"ok": true,
"loadNumber": "LD-2026-815401",
"previousStatus": "IN_TRANSIT",
"status": "CANCELLED",
"cancelledAt": "2026-08-21T04:49:21.654Z",
"trackingEndedAt": "2026-08-21T04:49:21.654Z"
}
Tracking stops immediately: further driver pings are refused and the load leaves the driver app. Nothing is deleted — the stops and the GPS track recorded so far stay readable through getPosition and getTripStats. Money already spent on creating the load and on position reads is not refunded. Calling it again on the same load is safe: { ok: true, idempotent: true, loadNumber, status: "CANCELLED" }. A DELIVERED load cannot be cancelled — that throws LoadAlreadyDeliveredError (409).
Is the driver actually sharing?
driverTracking on the position read is the same word the PingPoint cabinet shows, so an agent can explain a silent load without reading the GPS track:
NOT_BOUND | Driver link never opened in the app | inviteSentAt (SMS went out?), linkOpenedAt (opened in a browser but app not installed) |
BOUND_SILENT | App bound to the load, no position yet | wait for the first ping |
DECLINED | Driver tapped "Not now" on the location-consent screen | declinedAt; cleared once the driver agrees |
REPORTING | Positions arriving | gpsTrack, eta |
PAUSED_BY_DRIVER | Sharing paused from the app | eta.tracking.state === "driver_paused" |
null means the load has no driver yet. Exported as the DriverTrackingState type.
Webhooks
PingPoint pushes events to the webhook URL configured in the cabinet (Integrations → Webhooks); the SDK only types the event names as WebhookEventType: pingpoint.load.created, pingpoint.load.updated, pingpoint.status.changed, pingpoint.load.completed, pingpoint.exception.raised, pingpoint.exception.resolved. Every payload carries the load block (including driverTracking); the exception events add exception: { id, type: "LATE" | "NO_SIGNAL" | "LONG_DWELL", detectedAt, resolvedAt, details }. Details and signature verification: https://pingpoint.suverse.io/docs/webhooks
Getting a key
- Sign up at pingpoint.suverse.io (e-mail or Google/GitHub).
- Cabinet → Integrations → Agent API → Issue key.
- The
sup_agent_… key arrives by e-mail; PingPoint never stores the secret. Lost it — re-issue from the same page.
Free operations work at zero balance. Paid ones debit your prepaid balance (cabinet → Billing); when it can't cover a call you get a typed 402 and nothing is charged.
Pricing
| Create a load | $0.65 |
| Read load position | $0.02 per request |
| Trip summary stats | $0.02 per request |
| Cancel a load | free |
| Pricing / balance | free |
Errors
Every failure is a typed error (all extend PingPointAgentError with .status and the raw .body):
InsufficientFundsError (402 — top up in the cabinet; carries balanceUsd/priceUsd), MissingFieldsError (400, .fields lists what's missing), TooManyStopsError (400 — over 2 pickups or 3 deliveries; carries .limits and .received), InvalidDriverPhoneError, UnknownBrokerError (422), ForbiddenError (403 — not your load), NotFoundError (404), LoadAlreadyDeliveredError (409 — cancel on a delivered load; don't retry, delivered is final), BillingUnavailableError (503 — nothing charged, retry later), ApiError (everything else, including the 501 OPERATION_NOT_AVAILABLE from updateStatus; status: 0 = network failure).
Load creation answers through a gateway that wraps the body — the machine code lands in upstreamCode and the original body in upstream. The SDK unwraps both shapes, so you get the same typed error either way.
Retries
Pass the same idempotencyKey when retrying createLoad — the balance is debited and the load created at most once:
await pp.createLoad(input, { idempotencyKey: "order-483920" });
Options
new PingPointAgent({
apiKey: "sup_agent_…",
baseUrl: "https://api.suverse.io",
fetch: customFetch,
});
Node ≥ 18 (uses global fetch). Zero runtime dependencies.
MCP server for AI agents: @suverselabs/pingpoint-mcp.
Full API documentation: https://pingpoint.suverse.io/docs