Pulse API

A REST API to read and manage your assets, people, and licenses, plus webhooks to receive events in real time. All endpoints return JSON and are scoped to your organization.

Authentication

Every request needs a bearer token. Create one under Configure → API (available on the Obsidian plan and above). The token is shown once — store it securely.

curl https://<your-workspace>.onyx-labs.io/api/v1/assets \
  -H "Authorization: Bearer pulse_your_token_here"

The base URL is your workspace subdomain followed by /api/v1. Requests are rate-limited per IP.

Scopes

Each token carries scopes. A read-only token gets every :read scope; a read-and-write token also gets the :write scopes.

Responses

Successful responses wrap the result in a data key; errors return an error message with an appropriate status code.

{ "data": { "id": "…", "assetTag": "PL-0001" } }
{ "error": "Invalid or revoked API token." }
401 missing/invalid token · 403 missing scope or plan · 404 not found
422 validation error · 429 rate limited · 500 server error

Assets

GET/api/v1/assets?limit=50
List assets, newest first. limit is 1–200 (default 50).
GET/api/v1/assets/:id
A single asset, including its current holder (from the open assignment).
POST/api/v1/assets
Create an asset — or, if serialNumber matches an existing one, update it in place. Requires assets:write.
curl -X POST https://<your-workspace>.onyx-labs.io/api/v1/assets \
  -H "Authorization: Bearer pulse_…" \
  -H "Content-Type: application/json" \
  -d '{
    "serialNumber": "C02X1234JGH7",
    "make": "Apple",
    "model": "MacBook Pro 14",
    "status": "IN_STOCK"
  }'

People

GET/api/v1/people?limit=50
List people, newest first.
GET/api/v1/people/:id
A single person.
POST/api/v1/people
Create a person — or, if email matches an existing one, update it in place (the onboarding/offboarding path). Requires people:write.
curl -X POST https://<your-workspace>.onyx-labs.io/api/v1/people \
  -H "Authorization: Bearer pulse_…" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Jane Doe", "email": "jane@acme.com", "role": "EMPLOYEE" }'

Sensitive fields (permissions, 2FA secrets) are never returned, and the API can’t grant the Superadmin role.

Licenses

GET/api/v1/licenses?limit=50
List licenses with computed seatsUsed.

Webhooks

Register an endpoint under Configure → Webhooks (Onyx plan). Pulse sends a POST with a JSON body when a subscribed event fires. A webhook with no events selected receives all of them. Delivery is best-effort and retried on the next matching event.

Events

Payload

{
  "id": "3f2a…",            // unique delivery id
  "event": "person.created",
  "createdAt": "2026-07-05T21:00:00.000Z",
  "organizationId": "…",
  "data": { "personId": "…" }
}

Verifying the signature

Each request carries X-Pulse-Event and X-Pulse-Signature. The signature is sha256= followed by an HMAC-SHA256 of the raw request body keyed with your webhook secret. Compare it against your own computation to confirm authenticity.

import { createHmac, timingSafeEqual } from "crypto";

function verify(rawBody, signature, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && timingSafeEqual(a, b);
}