Official JavaScript/TypeScript SDK for Pickpoint — a geolocation platform with four APIs under one key:
| API | What it does |
|---|---|
| Geocoding | Address ↔ coordinates (forward, reverse, place lookup) |
| Address search | Typeahead / autocomplete for address inputs |
| Routing | Routes, matrices, optimized multi-stop, elevation |
| Device tracking | Register devices over HTTP; stream live GPS over WebSocket |
Built for maps, delivery, logistics, and anything that needs places, routes, or live location. Data is OpenStreetMap-backed; responses are plain JSON / GeoJSON. Docs: pickpoint.io/docs.
This package wraps that surface so you do not hand-roll fetch / WebSocket framing:
PickPoint— HTTP client for geocode, search, routing, devices (+clientAuthfor SPAs)@pickpoint/sdk/tracking— live GPS over WebSocket (tracking.v2)
Apache-2.0. Go sibling: github.com/pickpoint/go-sdk.
npm i @pickpoint/sdkOne client, one auth session, whole public HTTP API.
import{PickPoint}from'@pickpoint/sdk'// Browser: pair from YOUR backend (see clientAuth below)constpp=newPickPoint({clientAuth: {accessToken: pair.accessToken,refreshToken: pair.refreshToken,expiresAt: pair.expiresAt,// unix ms},})// Flat shortcuts (also safe to destructure — methods are bound)awaitpp.forward({q: 'Berlin'})awaitpp.reverse({lat: 52.5,lon: 13.4})awaitpp.search({q: 'Berlin'})// address autocompleteawaitpp.route({locations: [/* … */]})// Valhalla bodyawaitpp.devices.list()const{ forward, search }=ppawaitforward({q: 'Paris'})// Namespaced (clearer in larger apps)awaitpp.geocoding.lookup({osm_ids: 'R62422'})awaitpp.routing.matrix({/* … */})awaitpp.devices.command(uid,newTextEncoder().encode('ping'))pp.close()Style tip: use pp.forward(...) day-to-day; prefer pp.devices.* / pp.routing.* for domains with generic verbs (list, get, route). Destructuring works because shortcuts are arrow class fields.
Tracking (live WebSocket) is separate: @pickpoint/sdk/tracking.
| Environment | How to auth |
|---|---|
| Node / your backend | apiKey (x-api-key) OK |
| Browser / embedded web app | clientAuth pair from your backend |
// Nodeconstpp=newPickPoint({apiKey: process.env.PICKPOINT_API_KEY!})// ——— Your backend, after session check ———// POST https://api.pickpoint.io/v2/client-tokens// Headers: x-api-key: <SECRET>// Body: { "scopes": ["geocoding", "address", "routing", "devices"], "ttlSec": 600 }// or omit scopes → all client-tokenable permissions on the key// → { accessToken, refreshToken, expiresAt, expiresIn, scopes }constpair=awaitfetch('/api/pickpoint/client-tokens',{credentials: 'include'}).then((r)=>r.json())constpp=newPickPoint({clientAuth: {accessToken: pair.accessToken,refreshToken: pair.refreshToken,expiresAt: pair.expiresAt,},})| Scope on mint | SDK surface |
|---|---|
geocoding | forward / reverse / lookup |
address | search |
routing | route / optimizedRoute / matrix / locate / elevation |
devices | devices.* |
| — | /v2/api-keys*not allowed with client tokens |
SDK refreshes at ~50% of access TTL (single-flight) and once on HTTP 401.
Pass an array (max 20 in flight; keep-alive via undici on Node):
awaitpp.forward([{q: 'Paris'},{q: 'Rome'}])awaitpp.geocoding.batch.reverse([{lat: 48.85,lon: 2.35}])| Response | Geocode batch slot |
|---|---|
2xx | parsed JSON |
400 / other 4xx (except auth) | empty ([] / null) |
401 | refresh once; else abort |
402 / 403 | abort, ApiAuthError |
≥500 / network | retry then throw |
Address / routing / devices throw ApiError on 4xx (with status + body) instead of empty slots.
Live GPS is a separate WebSocket session (@pickpoint/sdk/tracking): wss://tracking.pickpoint.io/v2/ws, subprotocol tracking.v2. Works in browsers (global WebSocket) and Node (ws).
A dropped socket is not a new trip. The SDK reconnects and Resumes the same track_uid.
First publish starts the trip if none is live. close sends TrackStop then hangs up. Call startTrack only to supersede (new order / TRACK_NOT_FOUND) or to set a route.
import{connect}from'@pickpoint/sdk/tracking'constsession=awaitconnect({endpoint: 'wss://tracking.pickpoint.io',// host; SDK appends /v2/wsauth: {clientId: deviceUid,clientSecret: deviceSecret},// from devices.create})session.publish({latitude: 55.75,longitude: 37.61})// TrackStart if idlesession.close()// TrackStop + hang upaccessToken is the client-token from POST /v2/client-tokens (scope devices) — same pair.accessToken as HTTP clientAuth. Mint it on your backend; never put the API key in the browser.
constsession=awaitconnect({endpoint: 'wss://tracking.pickpoint.io',auth: {accessToken: pair.accessToken},subscribe: deviceUid,})session.on('location',(msg)=>{console.log(msg.point.latitude,msg.point.longitude)// live fan-out; publisher never sees Loc})Wire format: pickpoint-proto.
npm i
npm test
npm run buildLive geocode batch e2e (suite skipped unless the key is set):
PICKPOINT_API_KEY=… npm test -- test/e2e-geocode-batch.test.ts
# optional: PICKPOINT_BASE_URL=https://api.pickpoint.io- PR to
dev→.github/workflows/ci.yml(typecheck, test, build on Node 20/22) - Merge
dev→main(untagged HEAD) → bump patch, tagvX.Y.Z,npm publish(OIDC) + GitHub Release in the same job
(tag push viaGITHUB_TOKENdoes not start new workflows — publish cannot wait on the tag event) - Manual tag
v*(pushed by a human) → publish + GitHub Release
Minor/major: bump version in a PR, merge with [skip release] in the commit message, then:
git tag v2.2.0
git push origin v2.2.0Fork and open a pull request against dev — not main. Only pickpoint organization members can merge dev or main. Releases are dev → main.