https://api.netwatcher.io
All protected routes require a valid JWT token in the Authorization header:
Authorization: Bearer <jwt_token>
Agents authenticate via:
- PIN – One-time bootstrap credential
- PSK – Persistent pre-shared key (returned after PIN bootstrap)
All list endpoints return data wrapped in a consistent format:
{
"data": [ /* array of items */ ],
"total": 100, // optional: total count (for paginated endpoints)
"limit": 50, // optional: requested limit
"offset": 0 // optional: requested offset
}Single item endpoints return the object directly:
{
"id": 1,
"name": "Example",
// ... other fields
}All errors return:
{
"error": "error message here"
}Register a new user account.
Request Body:
{
"email": "user@example.com",
"password": "securepassword",
"name": "John Doe",
"role": "USER",
"labels": {},
"metadata": {}
}Response:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"data": {
"id": 1,
"email": "user@example.com",
"name": "John Doe",
"role": "USER",
"verified": false,
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
}Authenticate an existing user.
Request Body:
{
"email": "user@example.com",
"password": "securepassword"
}Response:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"data": { /* User object */ }
}Agent login/bootstrap endpoint. Supports both PIN bootstrap and PSK authentication.
Request Body (PIN Bootstrap):
{
"workspace_id": 1,
"agent_id": 10,
"pin": "123456789"
}Response (Bootstrap Success):
{
"status": "success",
"psk": "generated-psk-token",
"agent": { /* Agent object */ }
}Request Body (PSK Auth):
{
"workspace_id": 1,
"agent_id": 10,
"psk": "existing-psk-token"
}Response (PSK Success):
{
"status": "ok",
"agent": { /* Agent object */ }
}Agent-specific API endpoints authenticated via PSK headers. These allow agents to access controller services without JWT.
Required Headers:
X-Workspace-ID: <workspace_id>
X-Agent-ID: <agent_id>
X-Agent-PSK: <psk_token>
Returns the agent's public IP as seen by the controller. Useful for agents to discover their public IP without external services.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
quick |
bool | false | If true, returns only IP without GeoIP enrichment |
Response (Full):
{
"ip": "203.0.113.45",
"geoip": {
"city": { "name": "Vancouver", "subdivision": "BC" },
"country": { "code": "CA", "name": "Canada" },
"asn": { "number": 852, "organization": "TELUS Communications" },
"coordinates": { "latitude": 49.2827, "longitude": -123.1207 }
},
"reverse_dns": "host-203-0-113-45.example.com",
"timestamp": "2026-01-23T17:30:00Z"
}Response (Quick):
{
"ip": "203.0.113.45",
"timestamp": "2026-01-23T17:30:00Z"
}IP intelligence lookup for agents. Returns GeoIP + ASN + reverse DNS for any IP.
Response:
{
"ip": "8.8.8.8",
"geoip": {
"country": { "code": "US", "name": "United States" },
"asn": { "number": 15169, "organization": "GOOGLE" }
},
"reverse_dns": "dns.google",
"timestamp": "2026-01-23T17:30:00Z"
}List all workspaces for the authenticated user.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
q |
string | - | Search query |
limit |
int | 50 | Max results (1-200) |
offset |
int | 0 | Pagination offset |
Response:
{
"data": [
{
"id": 1,
"name": "Production",
"ownerId": 1,
"description": "Production network monitoring",
"settings": {},
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}Create a new workspace.
Request Body:
{
"name": "My Workspace",
"description": "Optional description",
"settings": {}
}Get a specific workspace by ID.
Required Role: Any workspace member
Response includes user's role:
{
"id": 1,
"name": "Production",
"description": "...",
"my_role": "ADMIN"
}Update workspace properties.
Required Role: ADMIN
Request Body:
{
"name": "new-name",
"description": "Updated description",
"settings": { "key": "value" }
}Delete a workspace (soft delete).
Required Role: OWNER
List all members of a workspace.
Required Role: Any workspace member
Invite a new member to the workspace.
Required Role: ADMIN
Request Body:
{
"userId": 0,
"email": "newuser@example.com",
"role": "USER",
"meta": {}
}Roles: USER, ADMIN, OWNER
Update a member's role.
Required Role: ADMIN
Request Body:
{
"role": "ADMIN"
}Remove a member from the workspace.
Required Role: ADMIN
Accept a pending workspace invitation.
Request Body:
{
"email": "user@example.com"
}Transfer workspace ownership to another user.
Required Role: OWNER
Request Body:
{
"newOwnerUserId": 5
}List all agents in a workspace.
Required Role: Any workspace member
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
limit |
int | 50 | Max results (1-200) |
offset |
int | 0 | Pagination offset |
Response:
{
"data": [
{
"id": 10,
"workspace_id": 1,
"name": "Office Router",
"description": "Main office network monitor",
"location": "Vancouver, BC",
"public_ip_override": "",
"version": "1.2.0",
"last_seen_at": "2024-01-01T12:00:00Z",
"initialized": true,
"labels": {},
"metadata": {}
}
],
"total": 1
}Create a new agent with bootstrap PIN.
Required Role: USER
Request Body:
{
"name": "New Agent",
"description": "Description here",
"location": "Seattle, WA",
"public_ip_override": "",
"version": "",
"pinLength": 9,
"pinTTLSeconds": 3600,
"labels": {},
"metadata": {}
}Response:
{
"agent": { /* Agent object */ },
"pin": "123456789"
}Get a specific agent.
Required Role: Any workspace member
Update agent properties.
Required Role: USER
Request Body:
{
"name": "Updated Name",
"description": "Updated description",
"location": "New Location",
"labels": { "env": "production" }
}Delete an agent.
Required Role: ADMIN
Manual heartbeat update.
Response:
{
"ok": true,
"ts": "2024-01-01T12:00:00Z"
}Issue a new bootstrap PIN for an agent.
Request Body:
{
"pinLength": 9,
"ttlSeconds": 3600
}Response:
{
"pin": "987654321"
}Get the latest network info for an agent.
Get the latest system info for an agent.
List all probes for an agent.
Create a new probe.
Request Body:
{
"workspace_id": 1,
"agent_id": 10,
"type": "PING",
"enabled": true,
"interval_sec": 60,
"timeout_sec": 10,
"count": 5,
"duration_sec": 0,
"server": false,
"targets": ["8.8.8.8", "1.1.1.1"],
"agent_targets": [],
"labels": {},
"metadata": {}
}Probe Types:
| Type | Description |
|---|---|
MTR |
Traceroute with per-hop stats |
PING |
ICMP ping |
SPEEDTEST |
Speed test |
SYSINFO |
System information |
NETINFO |
Network information |
TRAFFICSIM |
Traffic simulation |
Get a specific probe.
Update probe settings.
Request Body:
{
"enabled": false,
"intervalSec": 120,
"timeoutSec": 15,
"labels": { "priority": "high" },
"replaceTargets": ["8.8.4.4"]
}Delete a probe.
Flexible query across all probe data.
Query Parameters:
| Param | Type | Description |
|---|---|---|
type |
string | Filter by probe type (PING, MTR, etc.) |
probeId |
uint | Filter by specific probe |
agentId |
uint | Filter by reporting agent |
probeAgentId |
uint | Filter by probe-owning agent |
targetAgent |
uint | Filter by target agent |
targetPrefix |
string | Filter by target prefix |
triggered |
bool | Filter by triggered status |
from |
time | Start timestamp (RFC3339 or Unix) |
to |
time | End timestamp |
limit |
int | Max results |
asc |
bool | Sort ascending (default: false) |
Get time-series data for a specific probe.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
from |
time | - | Start timestamp |
to |
time | - | End timestamp |
limit |
int | 0 | Max results |
asc |
bool | false | Sort ascending |
Get the latest probe data by type and agent.
Query Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Probe type |
agentId |
uint | Yes | Agent ID |
probeId |
uint | No | Specific probe ID |
Get probe data for a specific target.
Query Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
target |
string | Yes | Target host/IP |
type |
string | No | Filter by probe type |
from |
time | No | Start timestamp |
to |
time | No | End timestamp |
limit |
int | No | Max results |
latestOnly |
bool | No | Return only latest |
Find similar probes (same targets or target agents).
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
sameType |
bool | true | Restrict to same probe type |
includeSelf |
bool | false | Include the reference probe |
latest |
bool | false | Include latest data points |
ws://api.netwatcher.io/ws
Required Headers:
X-Workspace-ID: <workspace_id>
X-Agent-ID: <agent_id>
X-Agent-PSK: <psk_token>
| Event | Direction | Description |
|---|---|---|
probe_get |
Agent → Controller | Request probe configurations |
probe_get |
Controller → Agent | Probe config response |
probe_post |
Agent → Controller | Submit probe results |
probe_post_ok |
Controller → Agent | Acknowledgment |
version |
Agent → Controller | Report agent version |
version |
Controller → Agent | Acknowledgment |
{
"probe_id": 123,
"triggered": false,
"triggered_reason": "",
"created_at": "2024-01-01T12:00:00Z",
"type": "PING",
"payload": { /* type-specific data */ },
"target": "8.8.8.8",
"target_agent": 0Real-time updates for panel clients.
ws://api.netwatcher.io/ws/panel
Required Headers:
Authorization: Bearer <jwt_token>
Subscribe to probe data updates for a workspace:
{
"type": "subscribe",
"workspace_id": 1,
"probe_id": 0
}Set probe_id to 0 to receive all probe updates for the workspace.
Real-time probe data broadcast:
{
"type": "probe_data",
"workspace_id": 1,
"probe_id": 123,
"agent_id": 10,
"probe_type": "PING",
"data": { /* probe result */ }
}Speedtest queue status changes:
{
"type": "speedtest_update",
"workspace_id": 1,
"agent_id": 10,
"queue_id": 456,
"status": "running"
}Network topology changes:
{
"type": "network_map_update",
"workspace_id": 1,
"nodes": [...],
"edges": [...],
"generated_at": "2024-01-01T12:00:00Z"
}IP geolocation and ASN lookup using MaxMind GeoLite2 databases.
Note: These endpoints require GeoIP databases to be configured. Check
/geoip/statusfor availability.
Look up geographic and ASN information for a single IP address.
Query Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
ip |
string | Yes | IP address to look up |
Response:
{
"ip": "8.8.8.8",
"city": {
"name": "Mountain View",
"subdivision": "CA"
},
"country": {
"code": "US",
"name": "United States"
},
"asn": {
"number": 15169,
"organization": "GOOGLE"
},
"coordinates": {
"latitude": 37.4056,
"longitude": -122.0775,
"accuracy_radius": 1000
}
}Bulk IP lookup (maximum 100 IPs per request).
Request Body:
{
"ips": ["8.8.8.8", "1.1.1.1", "208.67.222.222"]
}Response:
{
"data": [
{ "ip": "8.8.8.8", "city": {...}, "country": {...}, "asn": {...} },
{ "ip": "1.1.1.1", "country": {...}, "asn": {...} }
],
"total": 2
}Check which GeoIP databases are loaded.
Response:
{
"city": true,
"country": true,
"asn": true
}MAC address vendor (OUI) lookup using the IEEE database.
Look up the vendor for a MAC address.
Path Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
mac |
string | Yes | MAC address (any format) |
Supported MAC Formats:
00:1C:42:XX:XX:XX00-1C-42-XX-XX-XX001C42XXXXXX
Response:
{
"mac": "00:1C:42:AB:CD:EF",
"oui": "00-1C-42",
"vendor": "Parallels, Inc.",
"found": true
}Bulk MAC vendor lookup (maximum 100 per request).
Request Body:
{
"macs": ["00:1C:42:AB:CD:EF", "00:00:5E:00:01:01"]
}Response:
{
"results": [
{ "mac": "00:1C:42:AB:CD:EF", "oui": "00-1C-42", "vendor": "Parallels, Inc.", "found": true },
{ "mac": "00:00:5E:00:01:01", "oui": "00-00-5E", "vendor": "ICANN, IANA Department", "found": true }
],
"count": 2
}Check if the OUI database is loaded.
Response:
{
"loaded": true,
"entry_count": 35000
}WHOIS lookup for IP addresses and domain names.
Perform a WHOIS query for an IP address or domain name.
Query Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | IP address or domain name |
ip |
string | No | Alternative to query for IP lookups |
Input Validation:
- IP addresses are validated with
net.ParseIP() - Domain names are validated against a strict regex pattern
- Invalid input returns 400 error before any command execution
Response:
{
"query": "8.8.8.8",
"raw_output": "NetRange: 8.8.8.0 - 8.8.8.255\nCIDR: 8.8.8.0/24\n...",
"parsed": {
"netname": "LVLT-GOGL-8-8-8",
"netrange": "8.8.8.0 - 8.8.8.255",
"organization": "Google LLC",
"country": "US"
},
"lookup_time_ms": 450
}Parsed Fields:
| Field | Description |
|---|---|
netname |
Network name |
netrange |
IP range (CIDR or range format) |
organization |
Organization name |
country |
Country code |
registrar |
Domain registrar |
created |
Creation date |
updated |
Last update date |
abuse_email |
Abuse contact email |
Combined IP lookup service providing both GeoIP and WHOIS data in a single request.
Combined GeoIP and WHOIS lookup. Accepts both IP addresses and hostnames.
Query Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
ip |
string | Yes | IP address or hostname to look up |
Response:
{
"ip": "8.8.8.8",
"hostname": "dns.google",
"geoip": {
"ip": "8.8.8.8",
"city": { "name": "Mountain View", "subdivision": "CA" },
"country": { "code": "US", "name": "United States" },
"asn": { "number": 15169, "organization": "GOOGLE" },
"coordinates": { "latitude": 37.4056, "longitude": -122.0775 },
"cached": true,
"cache_time": "2024-01-01T12:00:00Z"
},
"whois": {
"query": "8.8.8.8",
"raw_output": "...",
"parsed": { "netname": "LVLT-GOGL-8-8-8", "organization": "Google LLC" },
"cached": true,
"cache_time": "2024-01-01T12:00:00Z"
},
"cached": true,
"cache_time": "2024-01-01T12:00:00Z"
}Notes:
- If the input is a hostname, it is resolved to an IP address first
- The
hostnamefield is included only when a hostname was provided - Both GeoIP and WHOIS lookups are cached in ClickHouse
- Errors for individual lookups are returned in an
errorsobject
List alerts across all workspaces the user has access to.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
status |
string | - | Filter by status: active, acknowledged, resolved |
limit |
int | 50 | Max results |
Response:
{
"data": [
{
"id": 123,
"workspace_id": 1,
"probe_id": 45,
"agent_id": 10,
"probe_type": "PING",
"probe_name": "Core Router",
"probe_target": "10.0.0.1",
"agent_name": "Edge-Node-01",
"metric": "packet_loss",
"value": 5.2,
"threshold": 1.0,
"severity": "critical",
"status": "active",
"triggered_at": "2026-01-12T20:30:00Z"
}
]
}Get count of active alerts across all workspaces.
Response:
{
"count": 5
}Update alert status (acknowledge or resolve).
Request Body:
{
"status": "acknowledged"
}List alert rules for a workspace.
Required Role: Any workspace member
Create a new alert rule.
Required Role: ADMIN
Request Body:
{
"name": "High Packet Loss",
"metric": "packet_loss",
"operator": ">",
"threshold": 5.0,
"severity": "critical",
"notify_panel": true,
"notify_webhook": true,
"webhook_url": "https://hooks.example.com/alert",
"webhook_secret": "optional-hmac-secret",
"probe_id": null,
"agent_id": null,
"enabled": true
}Update an alert rule.
Required Role: ADMIN
Delete an alert rule.
Required Role: ADMIN
Copy probes from one agent to multiple destination agents.
Required Role: USER
Request Body:
{
"source_agent_id": 10,
"dest_agent_ids": [11, 12, 13],
"workspace_id": 1,
"probe_ids": [],
"probe_types": ["AGENT", "MTR"],
"match_targets": false,
"skip_duplicates": true
}Response:
{
"created": 6,
"skipped": 2,
"errors": 0,
"results": [
{
"source_probe_id": 100,
"dest_agent_id": 11,
"new_probe_id": 150
},
{
"source_probe_id": 101,
"dest_agent_id": 11,
"skipped": true,
"skip_reason": "duplicate exists"
}
]
}Options:
| Field | Description |
|---|---|
probe_ids |
Specific probes to copy (empty = all) |
probe_types |
Filter by type (AGENT, MTR, PING, etc.) |
match_targets |
Only copy probes targeting dest agents |
skip_duplicates |
Skip if probe already exists on dest |
When creating AGENT probes, set bidirectional: true to automatically create reciprocal probes:
Request Body:
{
"type": "AGENT",
"agent_targets": [20],
"bidirectional": true
}This creates:
- Primary probe: Agent A → Agent B
- Reverse probe: Agent B → Agent A
Both probes are created atomically within a single transaction.
Returns service health status.
Response:
{
"ok": true
}