Developer Documentation

API v0 REST + MQTT + WebSocket Integration guide for technical clients

Introduction

The Ngoma API gives you programmatic access to all platform data and control surfaces. Build custom dashboards, integrate with your existing home management systems, trigger automations from external sources, or extract historical analytics.

The API exposes three integration channels:

ChannelBest forFormat
REST APIRead/write operations, data retrieval, one-off commandsJSON over HTTPS
WebSocket feedReal-time state updates, live dashboardsJSON over WSS
WebhooksEvent-driven integrations, push notifications to your systemsJSON over HTTPS POST
MQTTLow-latency IoT integration, perception data on local networkJSON over TCP/TLS

Quickstart

Make your first API call in under five minutes.

  1. Get your API key

    Log in to your Ngoma dashboard and go to Settings โ†’ Integrations โ†’ API Keys โ†’ Create Key. Give the key a descriptive name and select the required permission scopes.

  2. Make your first request

    Retrieve the current perception snapshot for your deployment:

    curl -X GET https://api.workforceanalytics.co.za/v2/perception/snapshot \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "X-Deployment-ID: YOUR_DEPLOYMENT_ID"
  3. Review the response

    {
      "deployment_id": "dep_abc123",
      "timestamp": "2026-03-18T09:41:00Z",
      "perception": {
        "people_count": 7,
        "activity": "working",
        "sound_class": "conversation",
        "processing_load": 0.34
      },
      "sensors": {
        "depth_camera": "active",
        "lidar": "active",
        "microphone": "active"
      }
    }
  4. Subscribe to real-time updates

    Connect to the WebSocket feed to receive perception updates as they happen โ€” no polling required. See WebSocket Feed for details.

Authentication

All API requests require a Bearer token in the Authorization header, plus your deployment ID in X-Deployment-ID.

Authorization: Bearer nga_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Deployment-ID: dep_xxxxxxxx

API Key scopes

Each API key is issued with one or more permission scopes. Request only the scopes your integration requires.

ScopeAccess granted
perception:readRead real-time and historical perception data (occupancy, activity, sound)
security:readRead security events, alerts, and facial recognition results
security:writeTrigger sentry mode, send navigation commands
wellness:readRead personal wellness data from KnightGuard devices
environment:readRead device states, thermostat, and sensor values
environment:writeControl devices, activate scenes, adjust thermostat
events:readRead alert feed and event log
webhooks:manageCreate, update, and delete webhook endpoints
โš 
API keys with security:write or environment:write scopes can affect physical systems in your home. Store these keys in environment variables or a secrets manager โ€” never commit them to version control.

Base URLs

EnvironmentBase URL
Productionhttps://api.workforceanalytics.co.za/v2
Sandboxhttps://sandbox-api.workforceanalytics.co.za/v2
WebSocket (production)wss://rt.workforceanalytics.co.za/v2
Local MQTTmqtt://<jetson-ip>:1883 (TLS: port 8883)

The sandbox environment uses simulated data and does not connect to real hardware. Use it for development and testing. All endpoints, request formats, and response schemas are identical between environments.

Error Handling

The API returns standard HTTP status codes. Error responses include a machine-readable code and a human-readable message.

{
  "error": {
    "code": "deployment_not_found",
    "message": "No deployment found for ID: dep_xyz",
    "status": 404
  }
}
HTTP StatusMeaning
200 OKRequest succeeded
400 Bad RequestMalformed request body or invalid parameters
401 UnauthorizedMissing or invalid API key
403 ForbiddenAPI key lacks the required scope
404 Not FoundResource does not exist
429 Too Many RequestsRate limit exceeded โ€” see Retry-After header
503 Service UnavailableDeployment offline or Jetson unreachable

Rate Limits

PlanRequests / minuteWebSocket connections
Standard1202
Professional60010
EnterpriseUnlimitedUnlimited

Rate limit headers are included in every response:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 1742292180

Perception API

The Perception API provides access to real-time and historical AI perception data: people counting, activity classification, and ambient sound detection.

Endpoints

GET/perception/snapshot
Returns the latest perception snapshot for your deployment.
Scope: perception:read
// Response
{
  "deployment_id": "dep_abc123",
  "timestamp": "2026-03-18T09:41:00Z",
  "perception": {
    "people_count": 7,
    "activity": "working",           // working | relaxing | meeting | moving
    "sound_class": "conversation",   // music | conversation | silence | alert
    "processing_load": 0.34,
    "auto_ducking_active": true
  },
  "sensors": {
    "depth_camera": "active",        // active | inactive
    "lidar": "active",
    "microphone": "active"
  },
  "mode": "production"               // production | simulation
}
GET/perception/history
Returns hourly aggregated perception data for a date range (max 90 days).
Scope: perception:read
// Query parameters
?start=2026-03-01T00:00:00Z
&end=2026-03-18T23:59:59Z
&granularity=hour    // minute | hour | day

// Response
{
  "data": [
    {
      "timestamp": "2026-03-18T09:00:00Z",
      "avg_people_count": 5.2,
      "peak_people_count": 9,
      "dominant_activity": "working",
      "dominant_sound": "conversation"
    },
    ...
  ]
}
GET/perception/zones
Returns per-zone occupancy breakdown if multiple camera zones are configured.
Scope: perception:read

Security API

GET/security/alerts
Returns the recent security alert log. Supports pagination and severity filtering.
Scope: security:read
// Query parameters
?severity=critical,high   // critical | high | medium | low
&limit=50
&cursor=alert_cursor_token

// Response
{
  "alerts": [
    {
      "id": "alrt_xyz",
      "timestamp": "2026-03-18T08:55:00Z",
      "type": "person_detected",     // person_detected | sound_event | motion | unknown_face
      "severity": "high",
      "description": "Unknown person detected in Server Room zone",
      "zone": "server_room",
      "resolved": false
    }
  ],
  "next_cursor": "alert_cursor_token_next"
}
POST/security/patrol/start
Triggers an immediate manual patrol. Returns the patrol job ID.
Scope: security:write
// Request body (optional โ€” omit to use configured default route)
{
  "waypoints": ["entrance", "floor_1", "server_room"]
}

// Response
{
  "patrol_id": "patrol_abc",
  "status": "en_route",
  "current_waypoint": "entrance",
  "total_waypoints": 3
}
POST/security/patrol/stop
Stops an active patrol and returns the robot to standby.
Scope: security:write
GET/security/faces
Returns currently enrolled faces and their last-seen timestamps.
Scope: security:read

Wellness API

GET/wellness/vitals
Returns the latest KnightGuard reading for a registered wearable.
Scope: wellness:read
// Query parameter
?employee_id=emp_abc

// Response
{
  "employee_id": "emp_abc",
  "timestamp": "2026-03-18T09:40:30Z",
  "vitals": {
    "heart_rate_bpm": 72,
    "heart_rate_zone": "normal",        // low | normal | elevated | high | critical
    "spo2_percent": 98,
    "spo2_zone": "normal",
    "temperature_celsius": 36.8,
    "temperature_zone": "normal"
  },
  "activity": {
    "steps_today": 6240,
    "calories_burned": 312,
    "distance_metres": 4180,
    "speed_ms": 0.0,
    "motion_intensity": 0.12,
    "balance_stability": 0.04
  }
}
GET/wellness/history
Returns hourly wellness summaries for a date range.
Scope: wellness:read
โš 
Wellness data is personal health information subject to data protection regulations (POPIA, GDPR where applicable). Ensure your integration handles this data in compliance with your organisation's privacy policy and applicable law. Do not log or cache raw wellness readings beyond your stated retention period.

Environment API

GET/environment/devices
Returns all connected smart environment devices and their current states.
Scope: environment:read
// Response
{
  "devices": [
    {
      "entity_id": "light.reception_main",
      "friendly_name": "Reception Main Lights",
      "domain": "light",
      "state": "on",
      "attributes": {
        "brightness": 180,
        "color_temp": 4000
      },
      "last_changed": "2026-03-18T08:00:00Z"
    },
    ...
  ]
}
POST/environment/devices/{entity_id}/control
Send a control command to a specific device.
Scope: environment:write
// Request body
{
  "service": "turn_on",          // turn_on | turn_off | toggle | set_value
  "data": {
    "brightness": 200,           // optional โ€” device-specific attributes
    "color_temp": 3500
  }
}

// Response
{
  "entity_id": "light.reception_main",
  "state": "on",
  "success": true
}
POST/environment/scenes/{scene_id}/activate
Activate a configured workspace scene.
Scope: environment:write
GET/environment/climate
Returns current thermostat and blinds status.
Scope: environment:read

Events & Alerts API

GET/events
Returns the unified event log across all platform subsystems.
Scope: events:read
// Query parameters
?source=perception,security   // perception | security | wellness | environment
&since=2026-03-18T08:00:00Z
&limit=100

// Response
{
  "events": [
    {
      "id": "evt_xyz",
      "timestamp": "2026-03-18T09:30:00Z",
      "source": "security",
      "type": "unknown_face",
      "severity": "high",
      "payload": { "zone": "entrance", "confidence": 0.94 }
    }
  ]
}

Webhooks

Register an HTTPS endpoint to receive real-time event notifications via POST requests. Webhooks are ideal for pushing Ngoma events into your existing systems (Slack, PagerDuty, HRMS, SIEM, etc.).

Registering a webhook

POST/webhooks
Register a new webhook endpoint.
Scope: webhooks:manage
// Request body
{
  "url": "https://your-system.example.com/ngoma-events",
  "events": [
    "security.unknown_face",
    "security.patrol_complete",
    "wellness.vitals_critical",
    "perception.occupancy_change"
  ],
  "secret": "your_webhook_signing_secret"
}

// Response
{
  "webhook_id": "wh_abc123",
  "url": "https://your-system.example.com/ngoma-events",
  "status": "active",
  "created_at": "2026-03-18T09:00:00Z"
}

Webhook payload

POST https://your-system.example.com/ngoma-events
Content-Type: application/json
X-Ngoma-Signature: sha256=abc123...
X-Ngoma-Delivery: wh-delivery-id

{
  "webhook_id": "wh_abc123",
  "delivery_id": "wh-delivery-xyz",
  "event": "security.unknown_face",
  "timestamp": "2026-03-18T09:41:00Z",
  "deployment_id": "dep_abc123",
  "data": {
    "zone": "entrance",
    "confidence": 0.96,
    "image_available": true
  }
}

Verifying webhook signatures

Each delivery includes an X-Ngoma-Signature header containing an HMAC-SHA256 hash of the raw request body, signed with your webhook secret. Always verify this before processing the payload.

import hmac, hashlib

def verify_signature(payload_bytes, secret, signature_header):
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)

MQTT Topics

For low-latency, local-network integrations, subscribe directly to the Mosquitto MQTT broker deployed with your Ngoma unit. All perception and security events are published here in real time with sub-100ms latency.

Perception topics

ngoma/perception/people_count
Publish direction: Jetson โ†’ Subscribers
Published every frame (~5 Hz). Payload: {"count": 7, "ts": 1742292100}
ngoma/perception/activity
Publish direction: Jetson โ†’ Subscribers
Published when activity class changes. Payload: {"activity": "working", "confidence": 0.91}
ngoma/perception/sound
Publish direction: Jetson โ†’ Subscribers
Published when sound class changes. Payload: {"sound_class": "conversation", "confidence": 0.88}

Security topics

ngoma/security/face_detected
Publish direction: Jetson โ†’ Subscribers
Payload: {"person_id": "emp_abc", "name": "Jane Smith", "confidence": 0.96, "known": true}. known: false for unrecognised individuals.
ngoma/security/alert
Publish direction: Jetson โ†’ Subscribers
Payload: {"type": "unknown_face", "severity": "high", "zone": "entrance", "ts": 1742292100}
ngoma/navigation/status
Publish direction: Jetson โ†’ Subscribers
Patrol progress updates. Payload: {"status": "en_route", "waypoint": 2, "total": 5, "destination": "floor_1"}
ngoma/navigation/command
Publish direction: Client โ†’ Jetson
Send navigation commands. Payload: {"action": "go_to", "waypoint": "server_room"}

WebSocket Feed

Connect to the WebSocket feed for real-time state updates without polling. Ideal for live dashboards.

// Connect
const ws = new WebSocket(
  'wss://rt.workforceanalytics.co.za/v2/stream' +
  '?token=YOUR_API_KEY&deployment=dep_abc123'
);

// Subscribe to event types on connect
ws.onopen = () => {
  ws.send(JSON.stringify({
    action: 'subscribe',
    channels: ['perception', 'security.alerts', 'wellness.vitals']
  }));
};

// Receive events
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log(msg.channel, msg.data);
};

// Keepalive โ€” send ping every 30s
setInterval(() => ws.send(JSON.stringify({ action: 'ping' })), 30000);

WebSocket message format

{
  "channel": "perception",
  "event": "snapshot_update",
  "timestamp": "2026-03-18T09:41:00Z",
  "data": {
    "people_count": 8,
    "activity": "meeting",
    "sound_class": "conversation"
  }
}

Python SDK

Installation

pip install ngoma-analytics

Basic usage

from ngoma import NgomClient

client = NgomClient(
    api_key="nga_live_xxxxxxxxxxxxxxxxxxxx",
    deployment_id="dep_abc123"
)

# Get current perception snapshot
snapshot = client.perception.get_snapshot()
print(f"People in office: {snapshot.people_count}")
print(f"Current activity: {snapshot.activity}")

# Get last 7 days of hourly occupancy
history = client.perception.get_history(days=7, granularity="hour")
for hour in history:
    print(f"{hour.timestamp}: avg {hour.avg_people_count:.1f} people")

# Control a device
client.environment.control(
    entity_id="light.boardroom_main",
    service="turn_on",
    brightness=220
)

# Activate a scene
client.environment.activate_scene("morning_routine")

Real-time streaming (Python)

from ngoma import NgomClient

client = NgomClient(api_key="nga_live_xxxx", deployment_id="dep_abc123")

@client.on("perception")
def on_perception(data):
    print(f"Occupancy: {data['people_count']} | Activity: {data['activity']}")

@client.on("security.unknown_face")
def on_unknown_face(data):
    print(f"โš  Unknown person in {data['zone']}")
    # trigger your alert pipeline here

client.stream.connect()  # blocks until disconnected

Node.js SDK

Installation

npm install @ngoma/analytics-sdk

Basic usage

import { NgomClient } from '@ngoma/analytics-sdk';

const client = new NgomClient({
  apiKey: process.env.NGOMA_API_KEY,
  deploymentId: process.env.NGOMA_DEPLOYMENT_ID
});

// Get current snapshot
const snapshot = await client.perception.getSnapshot();
console.log(`Office occupancy: ${snapshot.peopleCount}`);

// Stream real-time events
client.stream.on('perception', (data) => {
  console.log('Activity changed:', data.activity);
});

client.stream.on('security.alert', (alert) => {
  if (alert.severity === 'critical') {
    notifySecurityTeam(alert);
  }
});

await client.stream.connect();

Code Examples

Post security alerts to Slack

import requests
from ngoma import NgomClient

client = NgomClient(api_key="nga_live_xxxx", deployment_id="dep_abc123")
SLACK_WEBHOOK = "https://hooks.slack.com/services/xxx/yyy/zzz"

@client.on("security.alert")
def forward_to_slack(alert):
    if alert["severity"] in ("high", "critical"):
        requests.post(SLACK_WEBHOOK, json={
            "text": f"๐Ÿšจ Ngoma Security Alert ({alert['severity'].upper()}): "
                    f"{alert['description']} in {alert['zone']}"
        })

client.stream.connect()

Log hourly occupancy to a database

import psycopg2
from ngoma import NgomClient

client = NgomClient(api_key="nga_live_xxxx", deployment_id="dep_abc123")
conn = psycopg2.connect("dbname=workforce host=localhost")

history = client.perception.get_history(days=30, granularity="hour")
with conn.cursor() as cur:
    for row in history:
        cur.execute(
            "INSERT INTO occupancy_log (ts, avg_people, activity) VALUES (%s, %s, %s)",
            (row.timestamp, row.avg_people_count, row.dominant_activity)
        )
conn.commit()

Activate "Presentation Mode" scene when a meeting is detected

from ngoma import NgomClient

client = NgomClient(api_key="nga_live_xxxx", deployment_id="dep_abc123")

last_activity = None

@client.on("perception")
def handle_activity(data):
    global last_activity
    if data["activity"] != last_activity:
        last_activity = data["activity"]
        if data["activity"] == "meeting":
            client.environment.activate_scene("presentation_mode")
            print("Presentation Mode activated")
        elif data["activity"] == "working":
            client.environment.activate_scene("focus_mode")
            print("Focus Mode activated")

client.stream.connect()

Data Models

PerceptionSnapshot

FieldTypeDescription
deployment_idstringYour deployment identifier
timestampISO 8601UTC timestamp of the snapshot
people_countintegerNumber of people detected
activityenumworking | relaxing | meeting | moving
sound_classenummusic | conversation | silence | alert
processing_loadfloat (0โ€“1)Jetson AI pipeline CPU/GPU utilisation
modeenumproduction | simulation

SecurityAlert

FieldTypeDescription
idstringUnique alert identifier
timestampISO 8601UTC time of the alert
typeenumperson_detected | unknown_face | sound_event | motion
severityenumcritical | high | medium | low
zonestringNamed zone where event occurred
resolvedbooleanWhether the alert has been acknowledged

WellnessReading

FieldTypeDescription
heart_rate_bpmintegerBeats per minute
heart_rate_zoneenumlow | normal | elevated | high | critical
spo2_percentfloatBlood oxygen saturation (%)
spo2_zoneenumnormal | low | critical
temperature_celsiusfloatBody temperature (ยฐC)
steps_todayintegerTotal steps since midnight

API Changelog

v0.0.3 โ€” 2026-03-18

v1.4.0 โ€” 2025-11-05

v1.3.0 โ€” 2025-08-12