Integration guide

Connect your devices to PulseGrid

PulseGrid never talks to your hardware. Your software posts events to a REST API as they happen — no gateway to install, no agent to run. Most teams have events landing within an afternoon.

Two kinds of credential

Management calls and event ingestion use different credentials, deliberately. A terminal in the field only ever holds a key that can post its own events — never one that could provision more terminals or read your estate.

CredentialHeaderUsed for
sk_live_… → JWTAuthorization: BearerLocations, terminal provisioning
pk_live_…X-Api-KeyPosting events for one terminal

1. Get a token

Create an organisation API key in the dashboard under Developer settings, then exchange it for a short-lived JWT.

curl -X POST https://pulse-grid.dev/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{ "apiKey": "sk_live_..." }'

# → { "accessToken": "eyJ...", "expiresIn": 3600 }

2. Create a location

A location is a site, store or depot. Skip this if you are adding terminals to one that already exists.

curl -X POST https://pulse-grid.dev/api/v1/locations \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Riverside Retail Park", "city": "Manchester" }'

3. Provision terminals

Register your devices in one call. Each comes back with its own ingest key — save them, they are what the devices will authenticate with. Terminals whose externalId already exists are skipped, so this is safe to re-run.

curl -X POST https://pulse-grid.dev/api/v1/terminals/bulk \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{
        "defaultLocationId": "<location-id>",
        "terminals": [
          { "externalId": "BAY-01", "name": "Wash bay 1" },
          { "externalId": "BAY-02", "name": "Wash bay 2" }
        ]
      }'

# → one object per terminal, each with its own "apiKey": "pk_live_..."

4. Post a transaction

Send a transaction whenever the device takes money. occurredAt is what analytics are built from, so a device that buffered while offline still reports into the right period.

curl -X POST https://pulse-grid.dev/api/v1/events/transaction \
  -H "X-Api-Key: pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
        "amount": 6.50,
        "currency": "EUR",
        "status": "Success",
        "paymentType": "card",
        "productName": "Premium wash",
        "quantity": 1,
        "occurredAt": "2026-07-22T14:05:11Z"
      }'

There is no field for a card number, cardholder name, expiry or CVV, and the API will not accept them. PulseGrid records that a payment happened, not its contents.

5. Send heartbeats

Heartbeats are how PulseGrid knows a device is alive. If one has not checked in within the window you set, it flips to offline and alerting fires.

curl -X POST https://pulse-grid.dev/api/v1/events/heartbeat \
  -H "X-Api-Key: pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
        "occurredAt": "2026-07-22T14:05:00Z",
        "reportedStatus": "Online",
        "firmwareVersion": "2.4.1"
      }'

Report errors

Errors carry your own codes, so a bill validator jam reads as a bill validator jam rather than a generic failure. Severity is Info, Warning or Critical.

curl -X POST https://pulse-grid.dev/api/v1/events/error \
  -H "X-Api-Key: pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
        "code": "BILL_VALIDATOR_JAM",
        "message": "Note path obstructed",
        "severity": "Critical",
        "occurredAt": "2026-07-22T14:06:20Z"
      }'

Batching

Devices on an intermittent connection should buffer locally and flush through POST /api/v1/events/batch, which takes multiple events in one request. Because each event carries its own timestamp, a flush after an outage lands correctly in history instead of bunching at the moment it arrived.

Common questions

Which credential do I use where?

Two, and they are not interchangeable. An organisation API key (sk_live_…) is exchanged for a short-lived JWT and used for management calls — creating locations and provisioning terminals. Each terminal then gets its own ingest key (pk_live_…) sent as X-Api-Key, and that key can only post events for that terminal.

Do I need one request per event?

No. POST /api/v1/events/batch accepts multiple events in a single call, which is what you want for devices that buffer while offline and flush on reconnect.

What happens to events recorded while a device was offline?

Send them when the device reconnects. Every event carries its own occurredAt timestamp, which is what analytics and history are built from — the separate received-at time is recorded but does not affect where the event lands in your reporting.

Is provisioning safe to run more than once?

Yes. POST /api/v1/terminals/bulk silently skips terminals whose externalId already exists, so you can run it repeatedly from a deploy script without creating duplicates.

I lost a terminal key. Do I have to re-provision?

No. GET /api/v1/terminals and GET /api/v1/terminals/lookup return each terminal’s apiKey, not just the creation response.

Can I test without hardware?

Yes. The developer console in the dashboard includes a simulator that sends real heartbeats and payment events through the same API, plus a live event feed showing exactly what arrived.

What decides whether a terminal shows as offline?

A heartbeat window you set for your organisation. If a terminal has not checked in within it, it flips to offline and alerting fires. It is configurable because a device reporting every 30 seconds and one reporting every 10 minutes are both perfectly healthy.

Try it without hardware

The developer console includes a simulator that pushes real heartbeats and payment events through this same API, alongside a live feed of exactly what arrived. Free plan covers three terminals, no card required.