Netrinos Account API v1

Version 1, 2026-09-05

Read-only REST API for Netrinos accounts on the Edge plan. It exposes the account’s users, devices, virtual devices, and port forwards, plus a short record of devices going online and offline. The intended use is keeping an external system, such as an ERP or asset database, synchronized with the devices registered in Netrinos.

Availability

Item Value
Plan Edge only
Base URL https://app.netrinos.com/api/v1
Transport HTTPS, GET only
Format JSON

Netrinos support issues the account’s API key on request. Once issued, it is visible on the account page in the portal.

What this is not

Versioning

The major version is in the path. Fields may be added to a response and new values may appear in enumerated fields without a version change, so ignore fields you do not recognize. A breaking change would ship as /api/v2.

Authentication

The API key is required on every request. There is no health check to call without one, and an empty or absent key is never valid. The one thing you can learn without a key is that a path does not exist: an unknown path, or a method other than GET, returns 404 before the key is checked.

curl -H "Authorization: Bearer 3f2a1c7e-9b44-4d21-8c6f-0a1e2d3b4c5d" \
  https://app.netrinos.com/api/v1/account

One key is active per account, and it grants read access to that account only. It is shown on your account page in the portal, so you can retrieve it whenever you need it. An account with no key set has no API access at all; ask support to issue one.

Ask support to replace the key if it is exposed. A new key takes effect immediately and the old one stops working, so plan the swap.

Treat the key as a credential. Send it only over HTTPS, keep it out of source control, and do not embed it in a browser client.

Conventions

Field names are camelCase. Timestamps in responses are RFC 3339 in UTC and may carry fractional seconds, for example 2026-09-04T14:22:31.582Z or 2026-09-04T14:22:31Z. Parse them as RFC 3339 rather than matching a fixed layout. A timestamp that is unknown or does not apply is null rather than a zero date. A string field with no value is "", never null. Array fields are always present, and empty rather than null.

Lists have a fixed order. Devices are sorted by name, users by username, and events newest first.

Time parameters

since accepts either form and matches rows strictly after the instant:

Form Example Meaning
RFC 3339 2026-09-01T00:00:00Z After that instant, any offset
Duration 25h, 7d, 90m That long before now, on our clock

A duration is a number followed by m, h, or d. d is exactly 24 hours, not a calendar day, and cannot be combined with other units; 1h30m is accepted, 1d12h is not. Durations save a daily job from keeping a watermark: since=25h covers the last day with an hour of overlap, every time, with nothing to compute.

Responses

List endpoints return the matching rows and a count.

{
  "data": [],
  "total": 0
}

There is no paging. Responses are capped at 1000 rows, which covers any normal account in one request. total is the number of rows that matched, so if it ever exceeds the number returned you have hit the cap and hold the first 1000 in list order; contact support if that happens.

Rate limit

Two requests per second sustained per account, with bursts of up to 20. The limit is per account, so every integration using the key shares it. Beyond that the API returns 429 with a Retry-After header. A daily sync uses a handful of requests; the limit exists to stop a runaway loop, not to constrain normal use.

Errors

Errors use HTTP status codes and a single body shape.

{
  "error": {
    "code": "plan_required",
    "message": "The Account API requires the Edge plan."
  }
}
Status Code Meaning
400 invalid_request A query parameter was malformed
401 unauthorized Key missing, unknown, or cleared
403 account_inactive Account suspended, canceled, or deleted
403 plan_required Account is not on the Edge plan
404 not_found No such endpoint, device, or not a GET
429 rate_limited Too many requests, see Retry-After
500 internal Server error, safe to retry

The API does not send CORS headers. Call it from a server, not a browser.

GET /account

Confirms the key works and identifies the account it belongs to.

Response:

{
  "id": "acmecorp",
  "name": "Acme Corporation",
  "plan": "edge",
  "created": "2024-03-11T09:14:02Z"
}
Field Type Notes
id string Account identifier
name string Account name
plan string Always edge on this API
created string Account creation time

GET /users

Users in the account.

Response:

{
  "data": [
    {
      "id": "jsmith.acmecorp",
      "username": "jsmith",
      "email": "jsmith@acme.example",
      "name": "J. Smith",
      "role": "owner",
      "created": "2024-03-11T09:14:02Z"
    }
  ],
  "total": 4
}
Field Type Notes
id string username.account, stable
username string Login name, unique in the account
email string Contact address, can change or be empty
name string Display name
role string owner, admin, editor, or user
created string When the user was added

Each account has exactly one owner. Use username to join a device to its user, and id as a durable key.

GET /devices

Devices registered in the account. Each record embeds its virtual devices and port forwards, so one pass over the list is a complete picture.

Query parameters:

Parameter Values Effect
since time, see Conventions Only devices registered after this

Response:

{
  "data": [
    {
      "id": "8f14e45f-ceea-467a-9f0a-1c2d3e4f5a6b",
      "name": "host0001",
      "dnsName": "host0001.acmecorp.2ho.ca",
      "type": "edge",
      "description": "Site gateway, warehouse",
      "platform": "linux arm64",
      "version": "1.3.2",
      "build": "260905.1121",
      "online": true,
      "lastSeen": "2026-09-04T14:21:58Z",
      "secureIp": "100.96.4.17",
      "lanIp": "192.168.20.10",
      "isp": "Rogers",
      "owner": "jsmith",
      "tags": ["warehouse"],
      "created": "2025-11-02T16:40:11Z",
      "virtualDevices": [
        {
          "name": "nvr",
          "dnsName": "nvr.host0001.acmecorp.2ho.ca",
          "description": "Site NVR",
          "secureIp": "100.96.4.31",
          "lanIp": "192.168.20.40",
          "created": "2025-11-04T10:02:44Z"
        }
      ],
      "portForwards": [
        {
          "name": "camera-lobby",
          "hostName": "cam-lobby",
          "vendor": "Axis",
          "ip": "192.168.20.51",
          "port": 8080,
          "protocol": "tcp",
          "service": "http",
          "enabled": true,
          "updated": "2026-09-01T03:12:00Z"
        }
      ]
    }
  ],
  "total": 137
}

Device fields

Field Type Notes
id string Device GUID, see below
name string Short DNS label, lowercase
dnsName string Full name on the Netrinos network
type string edge or client
description string Free text set in the portal
platform string OS and architecture, see below
version string Netrinos client version
build string Client build stamp, YYMMDD.HHMM
online boolean See Device online state
lastSeen string Last contact, null if never
secureIp string Address on the Netrinos network
lanIp string Address on its local network, if reported
isp string Internet provider short name, see below
owner string Username of the owning user
tags array Strings, empty if none
created string When the device registered
virtualDevices array See below, empty if none
portForwards array See below, empty if none

platform is reported by the device as operating system and architecture separated by a space, for example linux arm64, linux amd64, windows amd64, darwin arm64, android arm64, or ios arm64. Treat it as free text; new values can appear as platforms are added.

dnsName is empty for a moment after registration, before the device is placed in a network. Every other string field that can be empty says so in its notes.

isp is the short name of the provider behind the device’s public address at its last sync, for example Rogers, Bell, or TELUS. It is looked up from the address, so it identifies the connection the device is on rather than the account holder’s contract. It is empty when the device has not synced since registration or the address is not attributed to a provider.

Use id as the foreign key in your system. It survives renames, and name and dnsName do not. It also survives deletion: a device deleted in the portal is logged out, and if someone logs it in again locally it registers with the same id. See Detecting additions and removals.

Virtual device fields

A virtual device is a LAN device published on the Netrinos network under its own address.

Field Type Notes
name string DNS label on the Netrinos network
dnsName string Full name, name.<device dnsName>
description string Free text
secureIp string Address on the Netrinos network
lanIp string LAN address it maps to, see below
created string When the virtual device was allocated

lanIp is reported by the hosting device on each sync from client 1.3.2 onward. It is empty for a device on an older client or one that has not synced since the virtual device was added.

Port forward fields

Port forwards are reported by EdgeNodes from the LAN they sit on. They are observed data, refreshed on each device sync, and are not present on client devices.

Field Type Notes
name string Rule name as reported
hostName string Host the rule points at
vendor string Vendor inferred from the hardware
ip string LAN address of the target
port number Forwarded port
protocol string tcp or udp
service string Service inferred from the port
enabled boolean Whether the rule is active
updated string Last change to the rule, or null

GET /devices/{id}

One device by its id, exactly as returned in the list, in the same shape as a list entry and not wrapped in an envelope.

Returns 404 if the id is unknown, belongs to another account, or names a device that has been removed. The three cases are not distinguished.

GET /devices/events

What changed, newest first: devices added and removed, and devices going online and offline. One feed, one shape.

Query parameters:

Parameter Values Effect
since time, see Conventions Only events after this
type comma list, see below Only these events; default is all

An unknown type name is a 400, not an empty list. Names are matched without regard to case.

Response:

{
  "data": [
    {
      "id": "8f14e45f-ceea-467a-9f0a-1c2d3e4f5a6b",
      "name": "host0001",
      "event": "offline",
      "at": "2026-09-04T02:14:09Z"
    },
    {
      "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
      "name": "oldnvr",
      "event": "removed",
      "at": "2026-09-03T11:07:20Z"
    }
  ],
  "total": 2
}
Field Type Notes
id string Device id; for removed, the id it had
name string Name at the time
event string added, removed, online, or offline
at string When it happened
Event Fires when
added A device logs in and registers
removed A device is deleted in the portal
online A device makes contact after being offline
offline A device has been silent for 15 minutes

added and removed cover every device. online and offline cover EdgeNodes unless tagged noalert in the portal, and client devices only when tagged alert.

Events are kept for at least 7 days; the purge runs a few times a day, so rows can linger a few hours past that. They are for keeping up, not for history; copy what you need. removed rows carry the identity the device had when it went, so they still name it after it is gone. An offline row means the device had been silent for the threshold described under Device online state, not that it failed at that instant.

GET /devices/{id}/events

The same feed narrowed to one device, in the same shape, with the same since and type parameters and the same retention. A device that was deleted and logged in again shows its whole history under the one id, including the earlier removed.

Returns 404 for an id that is unknown, belongs to another account, or has been removed, exactly as GET /devices/{id} does.

Detecting additions and removals

Call GET /devices/events?since=25h&type=added,removed once a day. Each added row names a device to create; fetch it with GET /devices/{id} for the full record. Each removed row names a device to retire. The 25 hours give an hour of overlap, so nothing is missed if the job runs late, and re-applying a change you already applied should be a no-op on your side.

Deleting a device in the portal logs it out. If someone then logs it in again on the device itself, it registers under the same id and an added row follows the removed one. So treat removed as deactivate rather than delete, treat added for an id you already hold as reactivate, and when one id has both rows in a window apply them in at order, oldest first; the feed itself is newest first.

Once a day, also pull GET /devices in full and compare it against what you hold, keyed on id. The event feed is what keeps you current; the full list is what makes you certain. A diff cannot miss anything, and it is one request.

GET /devices?since=25h is the same idea for additions only, and returns full device records rather than event rows.

Device online state

online is true when the device’s Netrinos agent has reached Netrinos within the last 15 minutes. How it connects does not matter: a device on a direct path and one going through a relay report the same way.

Detection is asymmetric by design. Going offline takes at least 15 minutes of silence, because two to five minute outages are routine on cellular and carrier-grade NAT links and a shorter window would report working sites as down. Coming back online is immediate on the next contact. Each response is a snapshot rather than a history, so a device that failed and recovered between two polls reads as online in both. GET /devices/events has the transitions a poll cannot show you, for at least the last 7 days.

A device that comes up may start on a relayed path and settle to a direct one moments later. That transition is invisible here. online measures whether the device reaches Netrinos, not how, so settling changes nothing about the flag and produces no transition in the history.

Scope

online reports that the network path to the device is up. Netrinos has no visibility above that layer, so it says nothing about the health of the device or of anything attached to it.

A gateway pegged at 100 percent CPU still answers Netrinos, because the agent needs almost nothing to reply. The device reads as online while the cameras behind it are timing out. A full disk, a stopped service, and a dead camera all look identical from here. The reverse case is just as real: a transit outage marks a device offline while every piece of equipment at the site is working.

So use each source for what it can observe. Netrinos is authoritative for whether a site is reachable, which is the one failure equipment inside the site cannot report, because a dark site has nothing left to send with. Monitor cameras and services from the video management system, inside the network, where they can actually be seen.

Once a day is the right cadence for an inventory. Nothing in the device record changes faster than that except online, and the events feed keeps a week, so a daily job never misses anything. The whole run is two or three requests, with no state to keep between runs beyond your own records.

  1. Changes. GET /devices/events?since=25h&type=added,removed. Create or reactivate a record for each added id, fetching GET /devices/{id} for the details. Deactivate the record for each removed id.

    curl -H "Authorization: Bearer $NETRINOS_KEY" \
      "https://app.netrinos.com/api/v1/devices/events?since=25h&type=added,removed"
  2. Reconcile. GET /devices. Compare by id against your records and correct any drift. Refresh online, version, build, and the other fields while you are there.

  3. Owners, if you track them. GET /users.

If you also want outage visibility from this API, read online from the device list or poll GET /devices/events?type=offline,online with a since matching your interval. Every 15 minutes is as fast as is useful, because that is the detection window. Monitoring inside the site remains the better source for equipment health; see Scope.

Notes:

If your integration needs a field the API does not return, raise it with support rather than working around it.