Connection Diagnostics API Reference
Every report made under your brand is available through the Netavo public API: list them, fetch one as JSON or as your branded PDF, and erase one. Webhooks tell your systems when a report arrives and when it finds a problem.
Quick Start
The API is at https://partners.netavo.com/api/v1 and returns JSON. Create an API token in Beacon under API Tokens with the diagnostics.reports:read scope, then list the latest reports:
export NETAVO_API_TOKEN="your token"
curl "https://partners.netavo.com/api/v1/diagnostics/reports?limit=5" \
-H "Authorization: Bearer $NETAVO_API_TOKEN"
The API covers the reports of the Partner Branded options. The free Netavo-branded check has no API: its reports are opened by reference at check.netavo.com.
Authentication
Send the token as a bearer token on every request. A token belongs to your organisation, not to the person who created it, and carries only the scopes chosen for it.
| Scope | Allows |
|---|---|
diagnostics.reports:read | List reports, and fetch one as JSON or PDF |
diagnostics.reports:write | Erase a report |
platform.webhooks:manage | Create and change webhooks |
platform.events:read | Read the event log, to catch up on missed webhooks |
A missing, expired or revoked token gets 401. A valid token without the scope an endpoint needs gets 403 with the code insufficient-scope. Tokens are secrets: keep them on your servers.
Endpoints
A report is identified by its reference, such as EF-4M8T-2HVQ, in any case and with or without the dashes. Partners see the reports of their customers’ brands too.
| Request | Returns | Scope |
|---|---|---|
GET /diagnostics/reports | A page of report summaries, newest first | diagnostics.reports:read |
GET /diagnostics/reports/{reference} | The full report as JSON | diagnostics.reports:read |
GET /diagnostics/reports/{reference}/pdf | The report as your branded PDF | diagnostics.reports:read |
DELETE /diagnostics/reports/{reference} | 204; the report is erased | diagnostics.reports:write |
List Reports
GET /diagnostics/reports returns every report still within your retention period, newest first. The filters combine, and each only narrows the list.
| Query parameter | Meaning |
|---|---|
limit | Reports per page, 1 to 100; 25 if left out |
startingAfter | The previous page’s nextCursor, for the next page |
receivedFrom | Only reports received at or after this time (UTC) |
receivedTo | Only reports received before this time (UTC) |
severity | Only reports whose worst finding is ok, info, warn or problem |
collector | Only reports from web, windows, macos or other |
ip | Only reports whose public IPv4 or IPv6 address contains this text |
search | Only reports whose reference, or the name, company, ticket or description entered, contains this text |
curl "https://partners.netavo.com/api/v1/diagnostics/reports?severity=problem&receivedFrom=2026-11-01T00:00:00Z" \
-H "Authorization: Bearer $NETAVO_API_TOKEN"
{
"data": [
{
"reference": "EF-9C2D-7KLP",
"tenant": "examplefibre",
"createdAt": "2026-11-06T10:15:30Z",
"receivedAt": "2026-11-06T10:15:32Z",
"expiresAt": "2027-02-04T10:15:32Z",
"collector": "windows",
"collectorVersion": "1.1.0",
"publicIpv4": "198.51.100.23",
"publicIpv6": "2001:db8:77:4::10",
"asn": 64501,
"network": "AS64501 EXAMPLEFIBRE - Example Fibre Ltd",
"context": {
"name": "Sam Example",
"company": "Example Dental Ltd",
"providerTicket": "CASE-1042",
"description": "Calls drop after 30 seconds"
},
"worstSeverity": "problem",
"worstFinding": "Your router's SIP ALG is rewriting SIP messages",
"problemCount": 1,
"warningCount": 0,
"infoCount": 2,
"demo": false,
"schemaVersion": 2,
"attachedToTicket": false,
"link": "https://partners.netavo.com/api/v1/diagnostics/reports/EF-9C2D-7KLP"
}
],
"hasMore": false,
"nextCursor": null
}
The context fields are what the person who ran the check typed in, so treat them as untrusted text. When hasMore is true, pass nextCursor back as startingAfter for the next page.
Get a Report
GET /diagnostics/reports/{reference} returns the report exactly as it was made, in the Connection Check report format: snake_case fields, with a schema_version. Everything in client and context was reported by the device that ran the check; server and findings are the test servers’ own observations. Ignore fields you do not recognise: new versions only add them.
curl "https://partners.netavo.com/api/v1/diagnostics/reports/EF-4M8T-2HVQ" \
-H "Authorization: Bearer $NETAVO_API_TOKEN"
Trimmed here to a few fields of each section:
{
"schema_version": 2,
"reference": "EF-4M8T-2HVQ",
"created_utc": "2026-11-06T09:41:27Z",
"expires_utc": "2026-11-13T09:41:27Z",
"tenant": { "id": "examplefibre", "name": "Example Fibre", "demo": false, "visibility": "staff" },
"collector": { "kind": "macos", "version": "1.1.0", "os": "macOS 15.6" },
"context": { "name": "Alex Sample", "company": "Sample Accountants LLP", "provider_ticket": "EF-CASE-2207" },
"server": {
"addresses": [
{ "address": "198.51.100.77", "ip_version": 4, "asn": 64501, "holder": "EXAMPLEFIBRE - Example Fibre Ltd" }
],
"dns": { "resolvers": [ { "address": "198.51.100.2", "ip_version": 4, "asn": 64501 } ] }
},
"client": {
"ipv4_reachable": true,
"ipv6_reachable": true,
"dnssec_validating": true,
"latency": [ { "target": "check.netavo.com", "rtt_avg_ms": 7.0, "jitter_ms": 0.8, "sent": 250, "received": 250 } ],
"wifi": { "connected": true, "band": "5 GHz", "rssi_dbm": -51 }
},
"findings": [
{
"code": "sip-ok",
"severity": "ok",
"area": "lan",
"title": "SIP passes through unaltered",
"detail": null,
"observed_by": "server",
"values": {}
}
]
}
Each finding has a stable code to branch on, a severity (ok, info, warn or problem), and a title and detail written for people, in your own wording where you have set it.
Get a Report as PDF
GET /diagnostics/reports/{reference}/pdf returns the report as your branded PDF (application/pdf), the same document your staff download in Beacon.
curl "https://partners.netavo.com/api/v1/diagnostics/reports/EF-4M8T-2HVQ/pdf" \
-H "Authorization: Bearer $NETAVO_API_TOKEN" \
-o EF-4M8T-2HVQ.pdf
See the example partner-branded PDF.
Erase a Report
DELETE /diagnostics/reports/{reference} erases the report from your account now, ahead of your retention period, and answers 204. Copies already attached to support tickets belong to those tickets and are not removed. It needs diagnostics.reports:write.
curl -X DELETE "https://partners.netavo.com/api/v1/diagnostics/reports/EF-4M8T-2HVQ" \
-H "Authorization: Bearer $NETAVO_API_TOKEN"
Errors
Errors are RFC 9457 problem documents, served as application/problem+json. Branch on code, which never changes; title and detail are written for people. A report outside your organisation is 404, exactly like one that does not exist.
{
"type": "https://partners.netavo.com/api/problems/not-found",
"title": "Not found",
"status": 404,
"detail": "No report with that reference.",
"instance": "/api/v1/diagnostics/reports/EF-0000-0000",
"code": "not-found",
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}
| Status | Code | When |
|---|---|---|
| 400 | validation-failed | A filter or paging value is invalid; errors names the field |
| 401 | unauthorized | The token is missing, expired or revoked |
| 403 | insufficient-scope | The token lacks the endpoint’s scope |
| 404 | not-found | No report with that reference in your organisation |
| 429 | rate-limited | Too many requests; wait for the Retry-After header’s seconds |
Webhooks
Two events are sent for Connection Diagnostics. Both are thin: they say which report and how serious, and your system fetches the report from link.
| Event type | Sent when |
|---|---|
diagnostics.report.received | A report is made under your brand |
diagnostics.report.problems | A report finds at least one problem (severity problem) |
Create a webhook with POST /platform/webhooks, choosing these types; leave types out to receive every event your account can. The signing secret is returned once, when the webhook is created: store it then.
curl -X POST "https://partners.netavo.com/api/v1/platform/webhooks" \
-H "Authorization: Bearer $NETAVO_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://support.example.net/hooks/diagnostics",
"description": "Connection reports into the helpdesk",
"types": ["diagnostics.report.problems"]}'
Each event is an HTTPS POST of JSON to your URL:
{
"id": "evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e72",
"type": "diagnostics.report.problems",
"serviceArea": "diagnostics",
"organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
"occurredAt": "2026-11-06T10:15:32Z",
"data": {
"reference": "EF-9C2D-7KLP",
"tenant": "examplefibre",
"createdAt": "2026-11-06T10:15:30Z",
"collector": "windows",
"worstSeverity": "problem",
"problemCount": 1,
"warningCount": 0,
"infoCount": 2,
"demo": false,
"link": "https://partners.netavo.com/api/v1/diagnostics/reports/EF-9C2D-7KLP",
"deduplicationId": "diagnostics.report.problems:EF-9C2D-7KLP"
}
}
Answer with any 2xx quickly and do the work afterwards. The same event can arrive more than once, so ignore an id you have already handled. demo is true for a report made while your brand was in demo mode, which is not a real customer’s report. Missed events can be read back from GET /platform/events?type=diagnostics.report.problems.
Verifying Signatures
Every request carries a Webhook-Signature header, t=<unix seconds>,v1=<hex>. The v1 value is the lower-case hex HMAC-SHA256 of the timestamp, a full stop and the raw request body, keyed with your signing secret. While a secret is being rotated a second v1 follows, signed with the previous secret; accept the request if either matches. Compute it over the body exactly as received, before parsing it, and reject requests more than five minutes old.
import hashlib, hmac, time
def verify_webhook(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = [p.strip().split("=", 1) for p in header.split(",") if "=" in p]
timestamps = [v for k, v in parts if k == "t"]
signatures = [v for k, v in parts if k == "v1"]
if not timestamps or abs(time.time() - int(timestamps[0])) > tolerance:
return False
expected = hmac.new(secret.encode(), timestamps[0].encode() + b"." + raw_body,
hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, s) for s in signatures)
Connect Your Helpdesk
API access comes with both Partner Branded options. Talk to us about connecting reports to your ticketing system.