Data API
Everything the dashboard shows is a plain REST endpoint you can call yourself. Telemetry comes back as JSON, so you can pull device data into your own backend, a script, a spreadsheet, or another service.
How you authenticate depends on which server you’re talking to:
- Hosted (
pulsync.in) — an API key you generate in the portal, sent as aBearertoken. This is the path built for external/programmatic access. - Self-hosted — a single admin password gate. There’s no per-key auth; you either leave the gate open on a trusted LAN or log in to get a session cookie.
Hosted: API keys
1. Generate a key
In the portal, create an API key. It’s returned once — store it immediately, it’s never shown again. Keys look like psk_ followed by 40 hex characters and authenticate as your workspace (tenant), automatically scoped to your own devices.
You can also create one over the API from a logged-in portal session:
curl -X POST https://pulsync.in/api/keys
-H "Content-Type: application/json"
-d '{"label": "my-backend"}'
# → { "key": "psk_ab12cd34...", "record": { ... } } 2. List your devices
curl https://pulsync.in/api/devices
-H "Authorization: Bearer psk_ab12cd34..."
# → { "devices": [ { "id": "...", "name": "...", ... } ] } 3. Pull telemetry
curl "https://pulsync.in/api/devices/DEVICE_ID/data?limit=100"
-H "Authorization: Bearer psk_ab12cd34..." From Node:
const res = await fetch(
`https://pulsync.in/api/devices/${deviceId}/data?limit=100`,
{ headers: { Authorization: `Bearer ${process.env.PULSYNC_API_KEY}` } }
);
const { data } = await res.json(); Keys can be revoked from the portal (or DELETE /api/keys/:id); a revoked key stops working immediately.
Self-hosted: admin gate
The self-hosted server exposes the same telemetry, but device-management routes are mounted under /api/device/ and share the single ADMIN_PASSWORD gate — there are no per-key tokens.
If ADMIN_PASSWORD is unset (the default on a trusted LAN), the endpoints are open and you can call them directly:
curl "http://pulsync.local:3456/api/device/devices/DEVICE_ID/data?limit=100" If ADMIN_PASSWORD is set, log in first to get a session cookie, then send that cookie on later calls:
# 1. Log in — stores the ps_admin cookie in a cookie jar
curl -c jar.txt -X POST http://pulsync.local:3456/api/device/admin/login
-H "Content-Type: application/json"
-d '{"password": "your-admin-password"}'
# 2. Use the cookie to pull data
curl -b jar.txt "http://pulsync.local:3456/api/device/devices/DEVICE_ID/data?limit=100" List devices to find a device ID:
curl -b jar.txt http://pulsync.local:3456/api/device/devices
# → { "devices": [ ... ], "ws_connections": N } No API keys in self-hosted. Auth is one shared admin password plus a session cookie, which is awkward for headless scripts. On a trusted LAN, leaving the gate open is the simplest option. If you expose the server beyond your network, set
ADMIN_PASSWORDand put it behind a TLS proxy.
Query parameters
Both servers accept the same parameters on the data endpoint:
| Parameter | Default | Description |
|---|---|---|
limit | 100 | Max number of data points to return (hosted caps this at 1000) |
since | (none) | ISO 8601 timestamp — return only points received after this time |
Example:
?limit=500&since=2026-09-01T00:00:00Z Response shape
Both servers return telemetry in the same shape. Each point carries the raw payload the device sent plus the server receive time:
{
"data": [
{
"data": { "temperature": 25.3, "humidity": 60 },
"timestamp": "2026-09-07T10:42:11.000Z"
},
{
"data": { "temperature": 25.1, "humidity": 61 },
"timestamp": "2026-09-07T10:42:06.000Z"
}
]
} Points are newest-first. data is whatever key-value payload the device sent via Pulsync.send().
Live data (WebSocket)
Polling the data endpoint gives you history. On the self-hosted server you can also receive updates the moment a device sends them by connecting to the live WebSocket instead of polling in a loop.
Connect
ws://pulsync.local:3456/api/live On connect the server sends a welcome frame, then pushes an event every time something happens on any device:
const ws = new WebSocket("ws://pulsync.local:3456/api/live");
ws.onmessage = (msg) => {
const evt = JSON.parse(msg.data);
if (evt.type === "device_data" && evt.deviceId === MY_DEVICE_ID) {
console.log("new data:", evt.data, "at", evt.timestamp);
}
}; Event types
type | Fields | Meaning |
|---|---|---|
connected | clients | Sent once on connect |
device_data | deviceId, data, timestamp | New telemetry — data is the payload the device sent |
heartbeat | deviceId, status, timestamp | Device checked in |
enrolled | deviceId, pairingCode | A device enrolled |
ota_status | deviceId, status, version | Firmware offered or applied |
command_sent | deviceId, command, payload | A command was dispatched |
Things to know
- It’s a firehose. Every connected client receives events for all devices — there’s no per-device subscription. Filter by
deviceIdin your handler. - No history on connect. The stream only carries events that happen after you connect. For past data, use the REST endpoint above.
- The stream is not gated by
ADMIN_PASSWORD. Unlike the REST management routes,/api/liveaccepts any connection. That’s fine on a trusted LAN, but if you expose the server, anyone who can reach it can read the live stream. Put the server behind a TLS reverse proxy with its own access control if it’s reachable beyond your network.
Other operations
The same endpoints and auth let you drive devices, not just read from them:
| Action | Method + path |
|---|---|
| List devices | GET /devices |
| Device details | GET /devices/:id |
| Query telemetry | GET /devices/:id/data |
| Push config | PUT /devices/:id/config — body { "key": value, ... } |
| Send a command | POST /devices/:id/command — body { "command": "...", "payload": {} } |
Prefix with /api on hosted and /api/device on self-hosted. Config changes and commands reach the device over MQTT (on the next heartbeat, or immediately if the device is connected).
Keep keys secret
API keys and admin passwords are credentials — treat them like passwords. Don’t commit them to source control or ship them in frontend code; keep them in environment variables on a server you control. Hosted keys are shown once at creation, so store them right away, and revoke any key you suspect is leaked.