API Reference

Partner API

Fleet management API reference

The Partner API provides programmatic access to your NWX fleet. Query device status, organization health, and fleet statistics.

Base URL: https://partner.networkweather.com

OpenAPI Spec: Download the OpenAPI 3.2 specification for import into Postman, Swagger UI, or other API tools.


Authentication

The Partner API uses OAuth2 client_credentials grant. Exchange your client ID and secret for a JWT access token, then include it as a Bearer token on all API calls.

Obtain Access Token

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "fleet:read analytics:read orgs:read orgs:write"
}

Token details:

  • Algorithm: HS256
  • Expiry: 1 hour
  • Claims: msp_id (your MSP identifier), scopes (authorized operations)

curl example:

TOKEN=$(curl -s -X POST https://partner.networkweather.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET" \
  | jq -r '.access_token')

All subsequent requests use:

Authorization: Bearer $TOKEN

Credential Management

Credentials are issued on account signup. The client secret is shown exactly once; store it securely.

Get current credentials (without secret):

GET /v1/credentials
Authorization: Bearer <token>
{
  "exists": true,
  "clientId": "nwx_ci_abc123",
  "scopes": ["fleet:read", "analytics:read", "orgs:read", "orgs:write"],
  "createdAt": "2026-01-15T10:00:00Z",
  "lastUsedAt": "2026-02-24T12:00:00Z",
  "enabled": true
}

Regenerate client secret:

POST /v1/credentials/regenerate
Authorization: Bearer <token>
{
  "clientId": "nwx_ci_abc123",
  "clientSecret": "nwx_cs_newSecretHere789",
  "message": "Client secret regenerated. Store it securely - it won't be shown again."
}

Endpoints

All endpoints require Authorization: Bearer <token> and return JSON.

GET /v1/msp/{mspId}/stats

Fleet-level dashboard statistics.

curl -s https://partner.networkweather.com/v1/msp/msp_a1b2c3d4-e5f6-7890-abcd-ef1234567890/stats \
  -H "Authorization: Bearer $TOKEN"

Response:

{
  "data": {
    "totalDevices": 47,
    "totalOrgs": 3,
    "healthy": 38,
    "warning": 6,
    "critical": 3,
    "onlineNow": 41
  }
}
Field Type Description
totalDevices int Total registered devices across all organizations
totalOrgs int Number of organizations
healthy int Devices with no issues (seen < 1h, current version)
warning int Devices idle > 1h or running outdated version
critical int Devices offline > 24h or never seen
onlineNow int Devices seen within the last hour

GET /v1/msp/members

List all users who have logged in to your MSP account. Read-only; invite and revoke are not yet supported.

curl -s https://partner.networkweather.com/v1/msp/members \
  -H "Authorization: Bearer $TOKEN"

Response:

{
  "data": [
    {
      "email": "alice@acme.com",
      "name": "Alice Smith",
      "picture": "https://lh3.googleusercontent.com/a/example",
      "provider": "google",
      "lastLoginAt": "2026-02-27T10:30:00Z"
    }
  ],
  "meta": {
    "total": 1
  }
}
Field Type Description
email string User's email address
name string Display name
picture string Avatar URL (omitted if unavailable)
provider string Auth provider: google, microsoft, or password
lastLoginAt string ISO 8601 timestamp of last login

GET /v1/organizations

List all organizations with per-org health statistics. Sorted by health (critical first).

curl -s "https://partner.networkweather.com/v1/organizations?limit=100" \
  -H "Authorization: Bearer $TOKEN"

Query parameters:

Parameter Type Default Description
limit int 100 Max results (1-1000)

Organizations are automatically scoped to your authenticated MSP.

Response:

{
  "data": [
    {
      "orgId": "org_b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "name": "Acme Corp",
      "mspId": "msp_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "slug": "acme-corp",
      "stats": {
        "orgId": "org_b2c3d4e5-f6a7-8901-bcde-f12345678901",
        "total": 25,
        "healthy": 20,
        "warning": 3,
        "critical": 2,
        "onlineNow": 22,
        "currentVersion": 25,
        "networkQuality": {
          "good": 0,
          "degraded": 0,
          "poor": 0,
          "offline": 25,
          "overall": "offline"
        },
        "locations": {
          "office": 0,
          "home": 0,
          "remote": 0,
          "unknown": 25
        },
        "connectionStates": {
          "streaming": 18,
          "idle": 4,
          "offline": 3
        }
      },
      "overallHealth": "critical"
    }
  ],
  "meta": {
    "total": 3
  }
}

POST /v1/organizations

Create a new organization.

curl -s -X POST https://partner.networkweather.com/v1/organizations \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Corp"}'

Request body:

Field Type Required Description
name string yes Organization display name (2-100 characters, must contain a letter or digit)

Response (201 Created):

{
  "data": {
    "orgId": "org_c3d4e5f6-a7b8-9012-cdef-123456789012",
    "name": "Acme Corp",
    "mspId": "msp_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "slug": "acme-corp"
  }
}

GET /v1/organizations/{orgId}

Single organization detail with stats.

curl -s https://partner.networkweather.com/v1/organizations/org_b2c3d4e5-f6a7-8901-bcde-f12345678901 \
  -H "Authorization: Bearer $TOKEN"

Response: Same shape as a single item from the list endpoint, wrapped in {"data": {...}}.

DELETE /v1/organizations/{orgId}

Delete an organization. Fails with 409 if the organization still has devices assigned to it.

curl -s -X DELETE https://partner.networkweather.com/v1/organizations/org_b2c3d4e5-f6a7-8901-bcde-f12345678901 \
  -H "Authorization: Bearer $TOKEN"

Response (204 No Content): Empty body on success.

Error (409 Conflict):

{
  "error": "Conflict",
  "message": "Cannot delete organization with assigned devices. Reassign or remove devices first."
}

GET /v1/devices

List devices with filtering, sorting, and pagination.

curl -s "https://partner.networkweather.com/v1/devices?orgId=org_b2c3d4e5-f6a7-8901-bcde-f12345678901&status=critical&sortBy=lastSeen&limit=50" \
  -H "Authorization: Bearer $TOKEN"

Query parameters:

Parameter Type Default Description
orgId string (all orgs) Filter by organization
status string (all) Filter: healthy, warning, critical
connectionState string (all) Filter: streaming, idle, offline
q string (none) Search by asset ID, serial number, user name, or email
sortBy string status Sort field: status, lastSeen, version, assetId
sortOrder string asc asc or desc
limit int 100 Max results (1-1000)

Response:

{
  "data": [
    {
      "clientId": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
      "mspId": "msp_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "orgId": "org_b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "currentVersion": "1.0.5.42",
      "os": "macOS",
      "osVersion": "15.3",
      "arch": "arm64",
      "channel": "stable",
      "lastSeen": "2026-02-24T11:55:00Z",
      "lastCheckIn": "2026-02-24T11:55:00Z",
      "checkInCount": 342,
      "serialNumber": "C02X1234ABCD",
      "assetId": "ACME-MBP-0042",
      "connectionState": "streaming",
      "status": "healthy",
      "isCurrentVersion": true,
      "location": {
        "type": "unknown",
        "confidence": 0,
        "signals": []
      }
    }
  ],
  "meta": {
    "total": 25
  }
}

Fields lastCheckIn, checkInCount, serialNumber, assetId, osVersion, referrer, userName, userEmail, and networkQuality are optional and omitted when the device hasn't reported them or the data isn't available.

GET /v1/devices/{clientId}

Single device detail.

curl -s https://partner.networkweather.com/v1/devices/A1B2C3D4-E5F6-7890-ABCD-EF1234567890 \
  -H "Authorization: Bearer $TOKEN"

Response: Same shape as a single item from the list endpoint, wrapped in {"data": {...}}.

DELETE /v1/devices/{clientId}

Remove a device from fleet tracking.

curl -s -X DELETE https://partner.networkweather.com/v1/devices/A1B2C3D4-E5F6-7890-ABCD-EF1234567890 \
  -H "Authorization: Bearer $TOKEN"

Response (204 No Content): Empty body on success.

GET /v1/devices/{clientId}/logs

Retrieve telemetry logs for a specific device from Cloud Logging. Logs are ingested asynchronously; expect approximately 2.5 minutes of latency between when the device reports data and when it appears in this endpoint.

curl -s "https://partner.networkweather.com/v1/devices/A1B2C3D4-E5F6-7890-ABCD-EF1234567890/logs?limit=50" \
  -H "Authorization: Bearer $TOKEN"

Query parameters:

Parameter Type Default Description
from string (RFC3339) (none) Start time filter
to string (RFC3339) (none) End time filter
level string (all) Filter by severity: debug, info, warn, error
limit int 100 Max entries (1-500)

Response:

{
  "data": [
    {
      "timestamp": "2026-02-26T12:00:00Z",
      "severity": "INFO",
      "payload": {
        "clientId": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
        "type": "network_check",
        "latency_ms": 12.5
      }
    }
  ],
  "meta": {
    "count": 1,
    "limit": 50
  }
}

Logs typically become available within 2-3 minutes of device reporting. Older logs are subject to Cloud Logging retention policies (30-day default).

GET /v1/health

Service health check (no auth required). Returns the service name and deployment revision.

curl -s https://partner.networkweather.com/v1/health
{
  "service": "partner-api",
  "revision": "partner-api-production-00015-abc"
}

Device Status Logic

The API computes device health status from check-in data:

Status Condition
healthy Seen within 1 hour AND running current version
warning Seen within 24 hours but idle > 1 hour, OR outdated version
critical Not seen for > 24 hours, OR never checked in

Connection states:

State Condition
streaming Last seen < 5 minutes ago
idle Last seen 5-60 minutes ago
offline Last seen > 60 minutes ago

Known Limitations

These fields exist in the API response schema but return placeholder data today. They are part of the data model for forward compatibility.

Field Current Value Why
networkQuality Omitted from response Aggregation pipeline from hop metrics to per-device quality not yet built
location Always {"type": "unknown", "confidence": 0} Location inference (office/home/remote) designed but not implemented
isCurrentVersion Always true Version comparison against latest release manifest not yet wired
networkQuality in org stats Counters are 0 except offline Same as per-device
locations in org stats All unknown Same as per-device

Planned Additions

Webhooks

Event Trigger
device.checkin Device checks in to C2
device.offline Device not seen for configurable threshold
device.version_changed Device reports a new app version
alert.triggered Policy threshold exceeded
org.created New organization provisioned

Planned delivery: Cloud Pub/Sub + Cloud Tasks, HMAC-SHA256 signature validation, retry with exponential backoff.

Telemetry Analytics (designed, not yet implemented)

Planned endpoints for aggregated telemetry data:

  • GET /v1/analytics/versions - Version distribution across fleet (count by version, OS, architecture)
  • GET /v1/analytics/network-quality - Aggregated network quality metrics (P50/P95 latency, jitter, packet loss) across devices and organizations
  • GET /v1/analytics/gateways - Gateway vendor distribution (OUI-based identification of router manufacturers)
  • GET /v1/analytics/time-series - Time-series data for device health, connectivity, and check-in frequency over configurable windows (1h, 24h, 7d, 30d)

Network History (designed, not yet implemented)

  • GET /v1/devices/{clientId}/network-history - Per-device connection quality time series, including hop-by-hop latency, WiFi signal strength, and gateway response times over time

Rate Limiting (not yet enforced)

Token bucket rate limiting per MSP is planned but not currently enforced. When implemented, rate-limited requests will return 429 Too Many Requests with a Retry-After header.


Error Responses

All errors return JSON:

{
  "error": "Unauthorized",
  "message": "Invalid or expired token"
}
Status Meaning Action
400 Bad request (invalid params) Check request format
401 Invalid or expired token Re-authenticate via /oauth/token
403 Access denied (wrong MSP/org) Token is valid but lacks access to the requested resource
404 Resource not found Check IDs
405 Method not allowed Check HTTP method
409 Conflict (e.g., delete org with devices) Resolve the conflict first
500 Server error Retry with backoff

Quick Start Example

Complete shell script: authenticate, list organizations, find critical devices.

#!/bin/bash

BASE_URL="https://partner.networkweather.com"
CLIENT_ID="your_client_id"
CLIENT_SECRET="your_client_secret"

# Authenticate
TOKEN=$(curl -s -X POST "$BASE_URL/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET" \
  | jq -r '.access_token')

echo "Token acquired (expires in 1 hour)"

# Fleet overview (MSP ID is in the token claims)
MSP_ID="your_msp_id"
echo "--- Fleet Stats ---"
curl -s "$BASE_URL/v1/msp/$MSP_ID/stats" \
  -H "Authorization: Bearer $TOKEN" | jq '.data'

# Organizations sorted by health
echo "--- Organizations ---"
curl -s "$BASE_URL/v1/organizations" \
  -H "Authorization: Bearer $TOKEN" | jq '.data[] | {name, overallHealth, devices: .stats.total}'

# Critical devices
echo "--- Critical Devices ---"
curl -s "$BASE_URL/v1/devices?status=critical" \
  -H "Authorization: Bearer $TOKEN" | jq '.data[] | {clientId, os, lastSeen, assetId}'