Collector API v1 reference

Request/response shapes for the collector-facing API v1 endpoints: enroll, heartbeat and observations, plus tenant/site binding rules and error codes.

Updated Aug 24, 2026Product v0.110 minDeveloper, Platform admin, Internal IT

Overview

The v0.1 collector talks to three POST, JSON, snake_case endpoints under /api/v1/collectors. On deployments where the main site is access-restricted, identical mirrors are also exposed under /api/public/v1/collectors so a collector on an unauthenticated network path can still reach the control plane.

EndpointMirrorPurpose
POST /api/v1/collectors/enroll/api/public/v1/collectors/enrollExchange a one-time enrollment token for a collector identity and credential
POST /api/v1/collectors/heartbeat/api/public/v1/collectors/heartbeatReport liveness, version and health; receive commands and current scan scopes
POST /api/v1/collectors/observations/api/public/v1/collectors/observationsUpload a batch of discovered hosts and open ports

Authentication and tokens

Enrollment tokens are random, short-lived, single-use, and bound to a specific partner, customer and site at issue time. The control plane stores only a SHA-256 hash of the token, never the token itself, and invalidates it atomically on successful enrollment so it can never be replayed.

Heartbeat and observations calls authenticate with a bearer token issued at enrollment.

Authenticated request header
Authorization: Bearer <auth_token>
Current credential model
Today's credential is a per-collector bearer secret; only its hash is stored server-side. Tokens are revocable at any time from the Collectors panel.

Enroll

Called once, on first start, to exchange the one-time enrollment token for a durable collector identity.

Request body
{
  "token": "<one-time-token>",
  "hostname": "web-scanner-01",
  "os": "linux",
  "os_version": "Ubuntu 22.04",
  "arch": "amd64",
  "version": "0.1.0",
  "instance_id": "b3f6c1a2-..."
}
201 response
{
  "collector_id": "col_...",
  "customer_id": "cus_...",
  "site_id": "site_...",
  "partner_id": "prt_...",
  "api_base_url": "https://api.assuriq.io",
  "auth_token": "at_...",
  "auth_token_expires_at": "2026-09-24T00:00:00Z",
  "client_cert_pem": null,
  "client_key_pem": null,
  "credential_note": "bearer-token; mTLS device certificates are planned",
  "heartbeat_interval_s": 60,
  "clock_skew_tolerance_ms": 5000,
  "modules": ["core-heartbeat", "port-discovery"],
  "scan_scopes": [
    {
      "cidrs": ["10.20.0.0/24"],
      "excludes": ["10.20.0.1"],
      "blocked_targets": [],
      "max_concurrency": 20,
      "discovery_only": true,
      "credentialed": false,
      "method": "tcp_connect"
    }
  ]
}
client_cert_pem / client_key_pem are dev-only
client_cert_pem is null unless server-generated dev keypair issuance is enabled for the deployment; client_key_pem is only ever returned in that dev mode. Neither is part of the production credential model.

Heartbeat

Sent on the interval returned at enrollment (heartbeat_interval_s). All fields except the bearer token are optional; the server updates health, version, hostname, OS and arch, and last_seen from whatever is supplied.

Request body
{
  "collector_id": "col_...",
  "instance_id": "b3f6c1a2-...",
  "hostname": "web-scanner-01",
  "os": "linux",
  "os_version": "Ubuntu 22.04",
  "arch": "amd64",
  "version": "0.1.0",
  "status": "healthy",
  "sent_at": "2026-08-24T00:00:00Z",
  "cpu_pct": 4.2,
  "memory_pct": 18.5,
  "disk_free_pct": 71.0,
  "uptime_s": 86400,
  "queue_depth": 0,
  "active_job_id": null,
  "last_error": null
}
200 response
{
  "ok": true,
  "collector_id": "col_...",
  "customer_id": "cus_...",
  "site_id": "site_...",
  "state": "healthy",
  "server_time": "2026-08-24T00:00:01Z",
  "clock_skew_ms": 120,
  "heartbeat_interval_s": 60,
  "update": null,
  "commands": [],
  "scan_scopes": []
}
Tenant fields are ignored on write
Any customer_id, site_id or partner_id included in a heartbeat body is ignored by the server. A collector can never change its own tenant or site assignment through this endpoint — those values only ever come from what the collector was enrolled against.

Observations

Uploads a batch of discovered hosts and their open ports. batch_id is an idempotency key: resubmitting the same batch_id after a network retry is safe and returns the original result rather than duplicating data.

Request body
{
  "batch_id": "b_2f1c...",
  "observed_at": "2026-08-24T00:05:00Z",
  "scan_job_id": "job_...",
  "collector_id": "col_...",
  "scope_cidrs": ["10.20.0.0/24"],
  "hosts": [
    {
      "ip": "10.20.0.42",
      "hostname": "printer-3f",
      "mac": "AA:BB:CC:00:11:22",
      "rtt_ms": 4,
      "observed_at": "2026-08-24T00:05:00Z",
      "open_ports": [
        { "port": 9100, "proto": "tcp", "service": "jetdirect", "state": "open" }
      ],
      "attributes": {}
    }
  ]
}
200 response
{
  "ok": true,
  "batch_id": "b_2f1c...",
  "accepted": 1,
  "rejected": 0,
  "created_assets": 1,
  "updated_assets": 0,
  "errors": [],
  "duplicate": false
}

Assets are reconciled in order of confidence: MAC address first, then hostname plus site, then IP address — so the same physical host is not duplicated across successive scans as its IP changes.

Topology fields (Collector 0.2.0-dev)

Collectors 0.2.0-dev and later may include optional per-host topology arrays alongside ip/hostname/open_ports. All fields are optional and 0.1.x payloads remain valid. AssurIQ stores the raw arrays as observations first, then derives interfaces, VLANs, addresses, routes, neighbors and forwarding entries, and finally computes graph edges labelled verified (LLDP/CDP neighbor) or inferred (forwarding table, route, subnet).

FieldShapeDerived into
sys_name, sys_description, sys_object_id, snmp_version, vendor, model, serial, device_typestringsAsset identity (device class, vendor/model, SNMP identity)
interfaces[]index, name, description, alias, mac, type, mtu, speed_bps, admin_status, oper_status, pvid, poenet_interfaces, Switch Port Matrix
vlans[]id, name, sourcenet_vlans, VLAN Explorer
ip_addresses[]address, prefix_length, subnet, interface_index, vlan_idnet_ip_addresses, subnets
routes[]destination, prefix_length, next_hop, interface_index, metricnet_routes, L3 view, inferred route edges
neighbors[]protocol (LLDP/CDP), local_port_id, remote_chassis_id, remote_port_id, remote_system_name, confidencenet_neighbors, verified L1 edges
forwarding[]mac, interface_index, bridge_port, vlan_id, statusnet_forwarding, inferred L2 endpoint attachments, port-move detection
topology.subnets[] / topology.links[]cidr, gateway, vlan_id / source & target asset, interface, protocol, layer, confidencenet_subnets, explicit edges (deduplicated against derived ones)

The response adds a topology summary: assets_with_topology, edges, subnets and links. Neighbors whose remote chassis cannot be matched to a known asset at the same site are kept as unresolved nodes rather than guessed.

Tenant and site binding rules

Every observations call is validated against the calling collector's assigned tenant and site before anything is written.

ConditionResult
collector_id, customer_id or site_id in the payload does not match the authenticated collector403 rejected
The collector has no assigned site409 rejected
A host IP falls outside the collector's authorized scan scopeThat host is rejected individually; the rest of the batch is still processed
Observations are only ever accepted for the collector's own assigned site. There is no way for a collector to write data into a different site or customer, even if it supplies different ids in the request body.

Error codes

  • AIQ-ENR-005Enrollment token binding mismatch
    The token is valid but does not match the partner, customer or site the install attempted to enroll into.
  • AIQ-ENR-006Customer missing
    The token's bound customer no longer exists, so enrollment cannot be completed.
  • AIQ-ENR-007Site missing or belongs to a different customer
    The token's bound site no longer exists, or now belongs to a different customer than the token was issued for.
  • AIQ-ENR-008Credential issuance failed
    Enrollment validated but the server could not issue a collector credential.
  • AIQ-COL-008Collector ID mismatch
    A heartbeat or observations call included a collector_id that does not match the authenticated collector's own id.
  • AIQ-COL-009Customer ID mismatch
    A request included a customer_id that does not match the authenticated collector's assigned customer.
  • AIQ-COL-010Collector has no assigned site
    Observations were submitted by a collector that has not been assigned to a site.
  • AIQ-COL-011Site ID mismatch
    A request included a site_id that does not match the authenticated collector's assigned site.
  • AIQ-COL-012Assigned site invalid
    The collector's assigned site no longer exists or is otherwise invalid, so observations cannot be reconciled.
  • AIQ-ING-002Observation processing failed
    The control plane accepted an observations batch but failed while reconciling it into assets.

Production hardening: the CSR flow

Before production
The enroll response can currently return a server-generated client_key_pem for development convenience. Before production use, this must be replaced with a certificate signing request (CSR) flow: the collector generates its own key pair locally and sends only a CSR to the control plane, which returns a signed certificate. The private key must never traverse the network. client_cert_pem and client_key_pem as returned today are temporary dev conveniences, not the production credential model.
Was this helpful?