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}'