NetKnife

API docs

NetKnife’s REST API — every endpoint that powers this site is public and queryable directly. Endpoints marked auth required need an X-API-Key header — get one on the account page. Unauthenticated requests are limited to 120 req/min per IP; with a key, 600 req/min.

Core lookup

GET/v1/prefix/:prefix

Full route detail for a CIDR or bare IP — AS path, decoded communities, RPKI/IRR state, vantage point, bogon flag.

Example
{
  "prefix": "204.69.247.0/24",
  "as_path": [394177, 18636],
  "origin_asn": 18636,
  "path_type": "best",
  "communities": [ /* array of Community */ ],
  "rpki": { "state": "valid", "origin_asn": 18636, "matched_roas": [...], "checked_at": "..." },
  "irr": { "match": true, "source_irr": "RADB", "registered_origins": [18636], "checked_at": "..." },
  "vantage_point": "rrc00 (via 12.0.1.63)",
  "observed_at": "2026-09-24T00:39:00Z",
  "data_source": "ris_live_stream",
  "is_bogon": false,
  "hops": [ /* array of AS hop */ ]
}
GET/v1/prefix/:prefix/vantage-points

Every distinct currently-observed feed's own view of a prefix (not collapsed to one "best" row) — the multi-vantage-point comparison.

Example
{
  "prefix": "204.69.247.0/24",
  "vantage_points": [
    { "vantage_point": "rrc00 (via 12.0.1.63)", "peer_asn": 3356, "as_path": [3356, 18636],
      "origin_asn": 18636, "communities": [...], "is_withdrawal": false, "observed_at": "..." }
  ]
}
GET/v1/asn/:asn

ASN profile — org info, announced prefixes, upstreams/peers/downstreams (each capped at 50 with an honest *_total count).

Example
{
  "asn": 18636,
  "org_name": "Example Org",
  "org_country": "US",
  "network_type": "content",
  "announced_prefixes": ["204.69.247.0/24"],
  "upstreams": [{ "asn": 394177, "org_name": "...", "relationship": "provider" }],
  "upstreams_total": 1,
  "peers": [...], "peers_total": 1,
  "downstreams": [...], "downstreams_total": 1
}
GET/v1/asn/:asn/paths

Every currently-observed AS path involving this ASN.

Example
{
  "asn": 18636,
  "paths": [{ "prefix": "204.69.247.0/24", "as_path": [394177, 18636], "peer_asn": 3356,
              "vantage_point": "rrc00", "observed_at": "..." }]
}
GET/v1/topology?asn={asn}&depth={n}

Cytoscape-ready {nodes, edges} graph, depth-limited from an ASN (default 2, max 4).

Example
{
  "nodes": [
    { "id": "18636", "asn": 18636, "org_name": "Example Org", "tier": "origin", "network_type": "content" },
    { "id": "3356", "asn": 3356, "org_name": "Lumen (Level 3)", "tier": "tier1", "network_type": "tier1" }
  ],
  "edges": [{ "source": "18636", "target": "3356", "relationship": "customer", "weight": 3 }]
}
GET/v1/rpki/:prefix?origin_asn=

RPKI validation state only, lightweight.

Example
{
  "prefix": "204.69.247.0/24", "state": "valid", "origin_asn": 18636,
  "matched_roas": [...], "checked_at": "..."
}
GET/v1/search?q=

Free-text search across prefixes, ASNs, and org names. Capped at 20 results.

Example
{
  "results": [
    { "type": "prefix", "value": "204.69.247.0/24", "label": "204.69.247.0/24" },
    { "type": "asn", "value": "18636", "label": "AS18636 — Example Org" }
  ]
}
GET/v1/health

Per-dependency status + collector lag. No cache, no rate limit.

Example
{
  "status": "ok", "postgres": "ok", "clickhouse": "ok", "redis": "ok",
  "routinator": "ok", "collector_lag_seconds": 4.2
}
GET/v1/communities/pending?asn=

Read-only review queue of pending community submissions — no auth needed to read.

Example
{
  "submissions": [{ "id": 14, "asn": 174, "community_value": "174:12345", "meaning": "...",
    "category": "other", "source": "contributor", "confidence": "pending", "created_at": "...",
    "votes_up": 2, "votes_down": 0, "score": 2 }]
}
POST/v1/communities/:asnauth required

Submit a proposed community meaning. Duplicate (asn, community_value) → 409.

Example
// body: { "community_value": "174:21001", "meaning": "...", "category": "geographic" }
// 201, confidence: "pending", source: "contributor"
POST/v1/communities/:id/voteauth required

Up/down-vote a pending submission. Net score reaching +3 auto-promotes it to confidence: verified.

Example
// body: { "direction": "up" | "down" }
// 200: the updated row plus votes_up/votes_down/score
POST/v1/auth/apikey

Dev-convenience key minting — no email verification. Idempotent per email (returns the existing key if one exists).

Example
// body: { "email": "you@example.com", "display_name": "optional" }
// 200: { "api_key": "..." }

Historical / time-travel

Built entirely from data already in ClickHouse (both live and RouteViews-imported history share one table). No RIPEstat fallback — that's only a current-state snapshot.

GET/v1/prefix/:prefix/history?at=<ISO8601>

What a CIDR's route looked like at a specific past time. 404 if nothing was observed that far back.

Example
{
  "prefix": "204.69.247.0/24", "as_path": [...], "origin_asn": 18636,
  "communities": [...], "rpki": { "state": "valid", "note": "reflects current RPKI state for the historical origin, not a point-in-time ROA snapshot" },
  "vantage_point": "...", "observed_at": "...",
  "as_of": "2026-09-24T12:00:00Z", "data_source": "historical", "hops": [...]
}
GET/v1/prefix/:prefix/timeline

The AS-path change history for a CIDR — one entry per contiguous era where the path stayed the same, newest first, capped at 100.

Example
{
  "prefix": "204.69.247.0/24",
  "eras": [{ "as_path": [394177, 18636], "origin_asn": 18636, "is_withdrawal": false,
             "first_seen": "...", "last_seen": "..." }]
}

IP tools

GET/v1/tools/ptr/:ip

Reverse DNS.

Example
{ "ip": "1.1.1.1", "ptr": "one.one.one.one" }
POST/v1/tools/bulk-lookup

Up to 500 IPs → covering prefix, origin ASN, org, RPKI state in one request.

Example
// body: { "ips": ["1.1.1.1", "8.8.8.8"] }
{ "results": [{ "ip": "1.1.1.1", "prefix": "1.1.1.0/24", "origin_asn": 13335,
                "org_name": "Cloudflare Inc.", "rpki_state": "valid" }] }
GET/v1/tools/geoip/:ip

IP geolocation via ip-api.com (free, no key).

Example
{ "ip": "1.1.1.1", "country": "Australia", "country_code": "AU", "city": "Sydney",
  "lat": -33.8, "lon": 151.2, "org": "Cloudflare", "source": "ip-api" }
GET/v1/tools/blocklist/:ip

DNSBL check across zen.spamhaus.org, b.barracudacentral.org, dnsbl.sorbs.net.

Example
{ "ip": "...", "listed": false,
  "zones": [{ "zone": "zen.spamhaus.org", "listed": false, "reason": null }] }
GET/v1/tools/bogon/:prefix

Pure local range check (RFC 1918/5737/6598, loopback, link-local, multicast, reserved) — no external call.

Example
{ "prefix": "10.0.0.0/8", "is_bogon": true, "reason": "RFC 1918 private range" }

DNS tools

GET/v1/dns/lookup/:name?type=A

A/AAAA/MX/TXT/NS/SOA/CNAME lookup.

Example
{ "name": "example.com", "type": "A", "records": ["1.2.3.4"] }
GET/v1/dns/propagation/:name?type=A

Fans out to Cloudflare + Google DoH in parallel to compare answers.

Example
{ "name": "example.com", "type": "A",
  "resolvers": [{ "resolver": "Cloudflare", "records": ["1.2.3.4"], "error": null },
                { "resolver": "Google", "records": ["1.2.3.4"], "error": null }] }
GET/v1/dns/dnssec/:name

Reads the AD flag from Cloudflare's DoH resolver — defers to their validation, not a from-scratch chain checker.

Example
{ "name": "example.com", "has_ds": true, "validated": true, "detail": "..." }
GET/v1/dns/whois/:domain

Domain RDAP (registrar, dates, nameservers) — same bootstrap pattern as ASN RDAP.

Example
{ "domain": "example.com", "registrar": "...", "created": "...", "expires": "...",
  "nameservers": [...], "source": "rdap" }
GET/v1/dns/email-auth/:domain?dkim_selector=

SPF/DMARC check, plus DKIM if a selector is provided (selectors aren't discoverable).

Example
{ "domain": "example.com",
  "spf": { "record": "...", "valid": true, "issues": [] },
  "dmarc": { "record": "...", "valid": true, "policy": "reject" },
  "dkim": null }
GET/v1/dns/ns-check/:domain

Compares parent-zone NS delegation against the authoritative NS set.

Example
{ "domain": "example.com", "parent_ns": [...], "authoritative_ns": [...],
  "match": true, "glue_issues": [] }
GET/v1/dns/rdns-delegation/:prefix

Checks whether a prefix's reverse zone is properly delegated.

Example
{ "prefix": "204.69.247.0/24", "reverse_zone": "247.69.204.in-addr.arpa",
  "delegated": true, "ns": [...], "sample_ptr_check": { "ip": "...", "resolves": true } }

Looking glass (RIPE Atlas)

Requires the server to have a RIPE_ATLAS_API_KEY configured — without one, both endpoints return 503 with a clear "not configured" message.

POST/v1/atlas/measurements

Creates a one-off ping or traceroute from real RIPE Atlas probes worldwide.

Example
// body: { "type": "ping", "target": "1.1.1.1", "probe_count": 5, "af": 4 }
// 201: { "measurement_id": 12345678, "status": "scheduled" }
GET/v1/atlas/measurements/:id

Poll for results — null until RIPE Atlas has data (frontend polls ~every 5s).

Example
{ "measurement_id": 12345678, "status": "stopped", "type": "ping", "target": "1.1.1.1",
  "results": [{ "probe_id": 1001, "country": "US", "rtt_ms": 4.2, "hops": null }] }

Diagnostics

No auth, no external API key needed for any of these five.

GET/v1/tools/tls/:host?port=443

TLS certificate inspector — connects with rejectUnauthorized: false deliberately, so it can inspect invalid/self-signed/expired certs, not just reject them.

Example
{ "host": "example.com", "port": 443, "subject": "CN=example.com", "issuer": "CN=...",
  "valid_from": "...", "valid_to": "...", "subject_alt_names": [...], "protocol": "TLSv1.3",
  "cipher": "TLS_AES_256_GCM_SHA384", "is_expired": false, "is_self_signed": false }
GET/v1/tools/headers?url=<url>

HTTP header/security inspector. Follows no redirects — a redirect is reported, not chased.

Example
{ "url": "https://example.com", "status": 200, "redirect_location": null,
  "headers": { "...": "..." },
  "security": { "hsts": true, "csp": false, "x_frame_options": true,
                 "x_content_type_options": true, "referrer_policy": true } }
GET/v1/tools/traceroute/:target

Server-side traceroute (-n -w 1 -q 1 -m 30, capped at 15s). Reverse DNS is disabled so one slow hop can't stall the whole run.

Example
{ "target": "1.1.1.1",
  "hops": [{ "hop": 1, "ip": "10.0.0.1", "hostname": null, "rtt_ms": 1.234 },
           { "hop": 2, "ip": null, "hostname": null, "rtt_ms": null }] }
GET/v1/tools/asn-allocation/:asn

ASN allocation history via RDAP events (registration, last changed).

Example
{ "asn": 13335, "handle": "AS13335", "name": "...", "country": "US",
  "events": [{ "action": "registration", "date": "2010-07-14T18:35:57-04:00" }] }
GET/v1/tools/mac/:mac

MAC address / OUI vendor lookup via api.macvendors.com (free, no key).

Example
{ "mac": "AA:BB:CC:DD:EE:FF", "oui": "AABBCC", "vendor": "Example Vendor, Inc." }

Saved lookups

A per-user bookmark list — not the same as the internal collector watchlist.

POST/v1/saved-lookupsauth required

Save a prefix or ASN with an optional label. Re-saving the same (kind, value) just updates the label.

Example
// body: { "kind": "prefix", "value": "204.69.247.0/24", "label": "my network" }
// 201: { "id": 1, "user_id": 3, "kind": "prefix", "value": "204.69.247.0/24",
//        "label": "my network", "created_at": "..." }
GET/v1/saved-lookupsauth required

Your own saved lookups, newest first, each with a lightweight live-status snapshot.

Example
{ "saved_lookups": [
    { "id": 1, "kind": "prefix", "value": "204.69.247.0/24", "label": "my network",
      "created_at": "...", "status": { "origin_asn": 18636, "is_withdrawal": false } }
] }
DELETE/v1/saved-lookups/:idauth required

Deletes only your own row — 404 either way if it's not yours or doesn't exist.

Example
// 200: { "deleted": true, "id": 1 }

Alerting

Route hijack/leak-style watch notifications for a prefix or ASN. A background checker polls every active subscription and fires on a real change — never on the first check tick, since that's just establishing a baseline.

POST/v1/alertsauth required

Create or update a watch subscription. At least one of webhook_url / email_enabled is required (400 no_delivery_method otherwise).

Example
// body: { "kind": "prefix", "value": "204.69.247.0/24", "watch_origin_change": true,
//          "watch_rpki_change": true, "webhook_url": "https://example.com/hooks/netknife" }
// 201: the created/updated subscription, last_origin_asn/last_rpki_state both null until
//      the first check establishes a baseline
GET/v1/alertsauth required

Your own subscriptions, newest first.

Example
{ "subscriptions": [ /* subscription rows */ ] }
DELETE/v1/alerts/:idauth required

Deletes only your own subscription.

Example
// 200: { "deleted": true, "id": 14 }
GET/v1/alerts/:id/eventsauth required

Recent firings for one subscription (origin/RPKI changes, delivery status), newest first, capped at 50.

Example
{ "events": [{ "id": 1, "event_type": "origin_change", "detail": { "...": "..." },
                "created_at": "...", "delivered": true, "delivery_error": null }] }