NI NetIntel

Developer documentation

NetIntel Public API

REST/JSON lookups for IP enrichment, reputation, ASN and prefix intelligence, DNS and CIDR calculations. Every response carries its sources, timestamps and an explicit data_status.

Base URL: https://netintel.multed.com/api/v1

Quick start

Create a key in the account dashboard (or with ApiKey.generate_for! in the Rails console) and export it. The raw key is shown once; only its digest is stored.

export NETINTEL_API_KEY="ni_live_..."

curl -sS \
  -H "Authorization: Bearer $NETINTEL_API_KEY" \
  "https://netintel.multed.com/api/v1/ip/8.8.8.8"

Authentication

Send Authorization: Bearer ni_live_... on every request. Missing or unknown keys get 401; revoked, expired or disabled accounts get 403.

Scopes:

  • ip:read
  • asn:read
  • prefix:read
  • dns:read
  • tools:read

An empty scope list grants unrestricted read access (legacy keys).

Rate limits

Counted per minute. Authenticated requests are limited per key (API_RATE_LIMIT_KEY_PER_MINUTE, default 120); all requests are also limited per source IP (API_RATE_LIMIT_IP_PER_MINUTE, default 240).

Every response includes:

  • RateLimit-Limit
  • RateLimit-Remaining
  • RateLimit-Reset

A throttled request returns 429 rate_limit_exceeded with Retry-After.

Response envelope

{
  "data": {},
  "meta": {
    "request_id": "req_...",
    "generated_at": "2026-11-01T12:00:00Z",
    "data_status": "complete"
  }
}

data_status is complete, partial or unavailable. Missing data stays null; it is never turned into a zero or an empty success.

Errors and pagination

Errors use a stable code and a request id:

{
  "error": {
    "code": "invalid_ip",
    "message": "The supplied value is not a valid IP address.",
    "details": {}
  },
  "meta": { "request_id": "req_..." }
}
  • 400 invalid input
  • 401 / 403 authentication or scope failure
  • 404 resource not found
  • 429 rate limit exceeded
  • 502 / 503 upstream or temporary failure

List endpoints use page and per_page. per_page defaults to 50; values above 100 are clamped to 100, while non-numeric values return 400 invalid_pagination.

{
  "data": [],
  "meta": {
    "page": 1,
    "per_page": 50,
    "total": 125,
    "total_pages": 3,
    "request_id": "req_..."
  }
}

Endpoints

GET

IP enrichment

ip:read

/api/v1/ip/{address}

Registry ownership, announced prefix, reverse DNS, classification and per-source provenance for an IPv4 or IPv6 literal.

Hostnames are rejected with 400 invalid_ip. Partial provider failures return 200 with data_status partial.

Request

curl -sS \
  -H "Authorization: Bearer $NETINTEL_API_KEY" \
  "https://netintel.multed.com/api/v1/ip/8.8.8.8"

Response

{
  "data": {
    "ip": "8.8.8.8",
    "version": 4,
    "geolocation": {
      "country_code": null,
      "country_name": null,
      "region": null,
      "city": null,
      "latitude": null,
      "longitude": null
    },
    "network": {
      "asn": 15169,
      "organization": "Google LLC",
      "prefix": "8.8.8.0/24",
      "rir": "ARIN"
    },
    "rdns": { "hostname": "dns.google", "status": "ok" },
    "classification": {
      "hosting": null,
      "vpn": null,
      "proxy": null,
      "tor": false,
      "network_type": "unknown"
    },
    "sources": [
      {
        "source": "rdap",
        "status": "ok",
        "fetched_at": "2026-11-01T12:00:00Z",
        "expires_at": "2026-11-02T12:00:00Z"
      }
    ],
    "last_updated_at": "2026-11-01T12:00:00Z"
  },
  "meta": {
    "request_id": "req_01JABCDEF",
    "generated_at": "2026-11-01T12:00:01Z",
    "data_status": "complete"
  }
}
GET

IP reputation

ip:read

/api/v1/ip/{address}/reputation

Latest versioned assessment with separate risk score and confidence. Missing risk-bearing sources are never reported as a clean result.

A stale or missing assessment returns 202 with data.status processing while a refresh job runs.

Request

curl -sS \
  -H "Authorization: Bearer $NETINTEL_API_KEY" \
  "https://netintel.multed.com/api/v1/ip/203.0.113.10/reputation"

Response

{
  "data": {
    "ip": "203.0.113.10",
    "risk_score": 64,
    "risk_level": "high",
    "confidence": 0.8,
    "data_status": "complete",
    "methodology_version": "1.0",
    "signals": [
      {
        "type": "dnsbl_listing",
        "severity": 0.8,
        "confidence": 1.0,
        "source_reliability": 0.8,
        "contribution": 64.0,
        "source": "dnsbl",
        "evidence_key": "dnsbl:example.test",
        "observed_at": "2026-11-01T12:00:00Z",
        "expires_at": "2026-11-01T13:00:00Z",
        "evidence": { "zone": "example.test" }
      }
    ],
    "assessed_at": "2026-11-01T12:00:00Z",
    "expires_at": "2026-11-01T13:00:00Z"
  },
  "meta": {
    "request_id": "req_01JABCDEF",
    "generated_at": "2026-11-01T12:00:01Z",
    "data_status": "complete"
  }
}
GET

ASN details

asn:read

/api/v1/asn/{asn}

Registry identity and announced-prefix counts for an autonomous system. Fields the provider does not return stay null.

Unknown ASNs return 404 asn_not_found; provider outages return 502 provider_unavailable.

Request

curl -sS \
  -H "Authorization: Bearer $NETINTEL_API_KEY" \
  "https://netintel.multed.com/api/v1/asn/15169"

Response

{
  "data": {
    "number": 15169,
    "handle": "AS15169",
    "name": "GOOGLE",
    "organization": "Google LLC",
    "country_code": "US",
    "rir": "ARIN",
    "registry_status": "registered",
    "announced": true,
    "prefix_count": { "4": 1014, "6": 62 },
    "sources": [
      {
        "source": "ripe_stat",
        "status": "ok",
        "fetched_at": "2026-11-01T12:00:00Z",
        "expires_at": "2026-11-01T18:00:00Z"
      }
    ],
    "last_updated_at": "2026-11-01T12:00:00Z"
  },
  "meta": {
    "request_id": "req_01JABCDEF",
    "generated_at": "2026-11-01T12:00:01Z",
    "data_status": "complete"
  }
}
GET

ASN prefixes

asn:read

/api/v1/asn/{asn}/prefixes

Paginated list of prefixes announced by the ASN, optionally filtered by address family.

per_page defaults to 50 and is clamped to 100. Non-numeric or non-positive page values return 400 invalid_pagination.

Request

curl -sS \
  -H "Authorization: Bearer $NETINTEL_API_KEY" \
  "https://netintel.multed.com/api/v1/asn/15169/prefixes?family=4&per_page=2"

Response

{
  "data": [
    { "prefix": "8.8.4.0/24", "family": 4 },
    { "prefix": "8.8.8.0/24", "family": 4 }
  ],
  "meta": {
    "request_id": "req_01JABCDEF",
    "generated_at": "2026-11-01T12:00:01Z",
    "data_status": "complete",
    "page": 1,
    "per_page": 2,
    "total": 1014,
    "total_pages": 507
  }
}
GET

Prefix details

prefix:read

/api/v1/prefix/{address}/{prefix_length}

Normalized prefix, observed origin ASNs, BGP visibility coverage and RPKI status. The CIDR slash is a separate path segment.

RPKI unknown (validator unavailable or warming up) is kept distinct from not_found.

Request

curl -sS \
  -H "Authorization: Bearer $NETINTEL_API_KEY" \
  "https://netintel.multed.com/api/v1/prefix/8.8.8.0/24"

Response

{
  "data": {
    "prefix": "8.8.8.0/24",
    "family": 4,
    "announced": true,
    "origins": [
      {
        "asn": 15169,
        "handle": "AS15169",
        "name": "GOOGLE",
        "active": true,
        "source": "ripe_stat",
        "first_observed_at": "2026-10-01T00:00:00Z",
        "last_observed_at": "2026-11-01T12:00:00Z"
      }
    ],
    "rpki": {
      "status": "valid",
      "origin_asn": 15169,
      "reason": null,
      "description": null,
      "source": "rpki",
      "observed_at": "2026-11-01T12:00:00Z",
      "roas": {
        "matched": [
          { "asn": 15169, "prefix": "8.8.8.0/24", "max_length": 24 }
        ],
        "covering": []
      },
      "data_status": "complete"
    },
    "visibility": {
      "status": "visible",
      "announced": true,
      "origins": [15169],
      "coverage": {
        "observed_by": 120,
        "total": 130,
        "ratio": 0.9231,
        "limited": true
      },
      "collectors": [
        {
          "name": "ripe_ris",
          "source": "ripe_stat",
          "observed_by": 120,
          "total": 130
        }
      ],
      "observed_at": "2026-11-01T12:00:00Z",
      "limitations": [
        "Visibility describes the RIS peer set, not the whole Internet"
      ],
      "data_status": "complete"
    },
    "sources": [
      {
        "source": "ripe_stat",
        "status": "ok",
        "fetched_at": "2026-11-01T12:00:00Z",
        "expires_at": "2026-11-01T18:00:00Z"
      }
    ],
    "last_updated_at": "2026-11-01T12:00:00Z"
  },
  "meta": {
    "request_id": "req_01JABCDEF",
    "generated_at": "2026-11-01T12:00:01Z",
    "data_status": "complete"
  }
}
GET

Prefix route history

prefix:read

/api/v1/prefix/{address}/{prefix_length}/routes

Bounded route observations with collector, peer, origin and source context. An empty result means nothing was observed in the window — never a withdrawal.

Default window: 24 hours. Maximum window: 7 days. per_page is clamped to 100.

Request

curl -sS \
  -H "Authorization: Bearer $NETINTEL_API_KEY" \
  "https://netintel.multed.com/api/v1/prefix/8.8.8.0/24/routes?from=2026-11-01T00:00:00Z&to=2026-11-01T12:00:00Z"

Response

{
  "data": [
    {
      "id": "5b0f0f5e-2f5e-4a4e-9c1a-000000000001",
      "type": "snapshot",
      "prefix": "8.8.8.0/24",
      "collector": "ripe_ris",
      "peer_asn": null,
      "origin_asn": 15169,
      "as_path": [15169],
      "rpki_status": "valid",
      "observed_at": "2026-11-01T10:00:00Z",
      "recorded_at": "2026-11-01T10:00:05Z",
      "source": { "key": "ripe_stat", "name": "RIPEstat" }
    }
  ],
  "meta": {
    "request_id": "req_01JABCDEF",
    "generated_at": "2026-11-01T12:00:01Z",
    "data_status": "complete",
    "page": 1,
    "per_page": 50,
    "total": 1,
    "total_pages": 1,
    "from": "2026-11-01T00:00:00Z",
    "to": "2026-11-01T12:00:00Z"
  }
}
GET

DNS lookup

dns:read

/api/v1/dns/{domain}

Forward DNS answers for A, AAAA, MX, NS, TXT, CAA, CNAME and SOA with TTL and resolver details.

A definitive empty answer returns 200 with status not_found. Resolver failures return 502 provider_unavailable.

Request

curl -sS \
  -H "Authorization: Bearer $NETINTEL_API_KEY" \
  "https://netintel.multed.com/api/v1/dns/example.com?type=A"

Response

{
  "data": {
    "domain": "example.com",
    "type": "A",
    "status": "ok",
    "answers": [
      { "type": "A", "ttl": 300, "value": "192.0.2.1" }
    ],
    "resolver": "system",
    "queried_at": "2026-11-01T12:00:00Z"
  },
  "meta": {
    "request_id": "req_01JABCDEF",
    "generated_at": "2026-11-01T12:00:01Z",
    "data_status": "complete"
  }
}
GET

CIDR calculator

tools:read

/api/v1/tools/cidr

Network address, host range, address count and masks for an IPv4 or IPv6 address and prefix length.

Address counts are decimal strings so IPv6 2**128 values are not truncated.

Request

curl -sS \
  -H "Authorization: Bearer $NETINTEL_API_KEY" \
  "https://netintel.multed.com/api/v1/tools/cidr?address=192.168.1.10&prefix_length=24"

Response

{
  "data": {
    "address": "192.168.1.10",
    "family": 4,
    "prefix_length": 24,
    "network": "192.168.1.0/24",
    "network_address": "192.168.1.0",
    "broadcast_address": "192.168.1.255",
    "first_address": "192.168.1.1",
    "last_address": "192.168.1.254",
    "address_count": "256",
    "usable_addresses": "254",
    "netmask": "255.255.255.0",
    "wildcard_mask": "0.0.0.255"
  },
  "meta": {
    "request_id": "req_01JABCDEF",
    "generated_at": "2026-11-01T12:00:01Z",
    "data_status": "complete"
  }
}

Machine-readable contract

The OpenAPI 3.1 document is public and matches these examples: https://netintel.multed.com/api/v1/openapi.yaml. The JSON discovery index lists every endpoint and its scope at https://netintel.multed.com/api/v1.