Developer Documentation

API, MTR client and
MCP for AI assistants

Query BGP network data, submit traceroutes programmatically, and connect Claude or ChatGPT to 160 routing-intelligence tools. All responses are JSON unless otherwise noted.

Quickstart

RouteViews, RIPE RIS, PeeringDB and the five RIRs are all public. Anyone can download the same raw data BGPScout does. None of them can be handed to an assistant and asked a question in plain English - that is the part BGPScout builds. Create an account, point your assistant at one URL, and ask.

1

Create an account

Free - bgpscout.io/register. No credit card for the demo tier.

2

Add the MCP server

Give your assistant this URL:

https://bgpscout.io/mcp
3

Ask it a question

"What is 8.8.8.8?" - "Who peers with AS577?" - "Find growing networks near Toronto."

  1. Open Settings → Connectors in claude.ai or Claude Desktop.
  2. Click Add custom connector and paste https://bgpscout.io/mcp.
  3. Click Connect and sign in with Google or PeeringDB.
  4. Ask a question in the chat - the tools are already enabled.

Prefer the CLI or a fixed API key instead of OAuth? See Claude below for claude mcp add and OAuth-free setup.

  1. Go to ChatGPT Settings → Apps → Add App.
  2. Name it bgpscout.io, set the MCP Server URL to https://bgpscout.io/mcp.
  3. Set Authentication to OAuth and leave registration on Dynamic Client Registration.
  4. Create the app, then sign in with Google or PeeringDB when prompted.

Full field-by-field walkthrough: ChatGPT below.

  1. Get an API key from Account Settings.
  2. Add a .mcp.json (project or user scope) with a bgpscout server entry.
  3. Set url to https://bgpscout.io/mcp and header X-API-Key to your key.
  4. Reload the MCP servers list - 160 tools appear.

Exact JSON block: Cursor below.

  1. Add an OpenAPI tool server pointing at https://bgpscout.io/mcp.
  2. Spec URL: https://bgpscout.io/mcp/openapi.json.
  3. Set auth to Bearer with an API key from Account Settings as the token.
  4. Optionally paste a curated preset into the function-name filter to narrow what loads - see OpenWebUI below.
"What is 8.8.8.8?" - a real answer, not a mockup

This is the same call captured against production for the MCP demos page - lookup is the unified whois/geo/RPKI tool (#187); prefer it over the narrower lookup_ip / lookup_prefix pair below.

lookup(target="8.8.8.8")

{
  "ip": "8.8.8.8",
  "address_family": "IPv4",
  "routing":      { "covering_prefix": "8.8.8.0/24",
                    "origin_asn": 15169,
                    "asn_name": "Google LLC" },
  "geo":          { "city": "Mountain View", "region": "California",
                    "country": "US", "winning_source": "ipinfo" },
  "registration": { "registry": "arin", "prefix": "8.8.8.0/24",
                    "alloc_date": "2023-12-28" },
  "rpki":         { "state": "Valid", "roa": "8.8.8.0/24",
                    "max_length": 24, "ta": "arin" },
  "rdns":         { "forward_count": 5 }
}
On this pageQuickstartIntroductionData quality & methodologyAPI key authenticationRate limitsMCP serverMCP authenticationTool catalogueExample promptsClaudeChatGPTCursorOpenWebUIGET /api/traceroutes/statsPOST /api/traceroutes/submitGET /api/asn/:asnGET /api/asn/:asn/neighboursGET /api/embed/asn/:asnAlert webhooksMTR client

Start hereOverview

The BGPScout API lets you query network data, retrieve ASN details, and submit traceroute results. All responses are JSON unless otherwise noted.

Base URL: https://bgpscout.io/api

Public endpoints need no credentials. Endpoints that write data - or that read your account's data - require an API key in the X-API-Key header.

How the data is madeData quality & methodology

Trace processing pipeline

Every traceroute submitted to BGPScout goes through a multi-stage enrichment pipeline before being included in any analysis or public results:

  1. Submission - raw MTR/traceroute data is received via the API or RIPE Atlas.
  2. Enrichment - each hop IP is resolved to an ASN (via BGP prefix tables), geolocated (via geofeed, IPinfo, MaxMind), and classified (anycast, IXP, cloud, etc.).
  3. Quality scoring - a 0-10 data quality score is computed from PTR record coverage, geo coverage, hop completion ratio, and path coherence.
  4. Display gating - traces scoring ≤ 2 are auto-rejected. Scores 3-5 go to pending review. Scores 6+ are auto-approved. Only approved traces appear in search results and analysis.

Unenriched traces are never shown. Traces awaiting enrichment (quality_score IS NULL) are excluded from all public views, search results, and analysis tools. You can see processing status on your submissions page.

Boomerang detection

A "boomerang route" is detected when a traceroute from country A to a destination also in country A transits a different country. That indicates suboptimal routing where domestic traffic unnecessarily crosses international borders.

Data sources

  • Traceroutes - user-submitted MTR tests, RIPE Atlas measurements, automated probes in 30+ countries
  • ASN data - ARIN, RIPE NCC, APNIC, LACNIC, AFRINIC registry data
  • PeeringDB - facility presence, IXP memberships, port speeds
  • GeoIP - geofeed (RFC 8805), IPinfo, MaxMind, PTR hostname analysis
  • Domain intelligence - Certificate Transparency, DNS enumeration, WHOIS

AccessAuthentication

Some endpoints require an API key passed in the X-API-Key HTTP header. Public endpoints do not require authentication.

To obtain a key, log in and visit Account Settings. Organisation accounts can also issue org-wide keys from the org dashboard.

curl -H "X-API-Key: your_api_key_here" \
     -H "Content-Type: application/json" \
     -d '{"traces": []}' \
     https://bgpscout.io/api/traceroutes/submit

Keys are only shown once, when created, and are stored hashed - if you lose one, revoke it and issue a new one. Never send a key in a query string: it ends up in access logs, proxy logs and Referer headers.

LimitsRate limits

REST API endpoints under /api are limited to 300 requests per 15-minute window, bucketed by client IP address. Exceeding the limit returns 429 with a JSON error body.

The MCP server at /mcp has its own, separate limiter - 60 requests per minute per API key (or per IP when unauthenticated). See MCP server.

Response headers

HeaderDescription
RateLimit-PolicyThe active policy, e.g. 300;w=900 - 300 requests per 900-second window
RateLimit-LimitMaximum requests per window
RateLimit-RemainingRequests remaining in the current window
RateLimit-ResetSeconds until the limit resets

429 response

{
  "success": false,
  "error": "Rate limit exceeded. Please slow down and try again later."
}

IntegrationsMCP server (Model Context Protocol)

BGPScout ships an MCP server, so an AI assistant - Claude, ChatGPT, Cursor, OpenWebUI - can query the platform directly: look up ASNs, search IXPs and facilities, read traceroutes, find prospects, and write back to your watchlist.

One endpoint: https://bgpscout.io/mcp

WhatValue
ProtocolMCP 2024-11-05 over HTTP (JSON-RPC 2.0). Methods: initialize, tools/list, tools/call, ping
Server identity{"name": "bgpscout", "version": "1.0.0"}
Tools160 on a standard account. Org accounts see 14 more (174), admins 10 more again (184 in the full catalogue). Counted live from the tool registry on every render of this page.
Rate limit60 requests per minute, per API key (per IP when unauthenticated)

HTTP surface

Method & pathAuthPurpose
POST /mcpYesThe MCP JSON-RPC endpoint. This is the URL you give an MCP client.
GET /mcpNoDiscovery - returns protocolVersion, capabilities and serverInfo so a client can confirm the server before authenticating.
GET /mcp/toolsYesThe tool list as plain JSON, filtered to your access level.
POST /mcp/tools/callYesCall a tool with {"name": "...", "arguments": {...}}.
POST /mcp/call/:toolYesREST-style call - the JSON body is the tool's arguments. Used by OpenAPI clients.
GET /mcp/openapi.jsonNoOpenAPI 3.1 spec, one operation per tool, for tool servers that speak OpenAPI rather than MCP.
GET /mcp/docsNoHuman-readable reference: every tool, grouped by category, with its description. Always current - the Tool catalogue below is generated from the same registry.
GET /mcp/infoNoConnection details as JSON - OAuth URLs, endpoints, sample client config.

Authentication

Two methods, both accepted on the same endpoint:

MethodHowBest for
API keyX-API-Key: KEY - or Authorization: Bearer KEY, which many clients send insteadClaude Code, Cursor, OpenWebUI, scripts
OAuth 2.0Browser login with Google or PeeringDB, then an explicit consent screen. PKCE (S256) is mandatory; dynamic client registration at /oauth/registerclaude.ai connectors, ChatGPT

Get a key from Account Settings. An unauthenticated call returns 401 with a WWW-Authenticate header pointing at the protected-resource metadata, which is what lets an OAuth-capable client start the login flow on its own.

The server deliberately does not accept a key in the query string (?api_key=…) - that leaks into access logs, proxy logs, browser history and Referer headers. Header only.

Check your key works

curl -s -X POST https://bgpscout.io/mcp \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Tool catalogue

The 160 tools are grouped into categories below, generated live from the same registry /mcp/docs renders - this list cannot drift from the server the way a hand-typed copy did (#258). The list_tools tool does the same from inside a conversation - call it first, optionally with a category, instead of loading everything.

Discovery

  • list_tools

ASN lookup & compare

  • lookup_asn
  • search_asns
  • bulk_lookup_asns
  • compare_asns
  • get_asn_history
  • get_route_history
  • get_bgp_state
  • get_asn_neighbours
  • get_asn_ixp_colocated
  • get_asn_peersdeprecated

ASN presence

  • get_asn_facilities
  • get_asn_ixps
  • get_asn_cdn_caches
  • get_transit_providers
  • find_ddos_protected
  • get_asn_prefixes
  • get_prefix_counts
  • get_asn_ipv6_reachability

Peering recommendations

  • get_recommended_ixps
  • get_recommended_peers
  • recommend_transit

IP & prefix (whois)

  • lookup
  • lookup_ip
  • lookup_prefix
  • get_asn_bogons
  • get_asn_rpki_status
  • get_rpki_validation_history
  • get_rpki_comparison_runs

IRR / RPSL

  • get_irr_objects
  • expand_as_set
  • compare_bgp_irr_rpki
  • generate_prefix_filter

BGP communities

  • get_bgp_communities
  • decode_bgp_communities

Registry allocations

  • search_allocations
  • get_asn_allocations
  • search_transfers
  • lookup_rir_org
  • search_dormant_ipv4
  • get_dormancy_by_registry

Geographic search

  • find_near
  • find_networks_near
  • get_asn_density
  • get_asn_geo_prefixes
  • find_transit_near
  • find_upstreams_near
  • find_networks_at_facilities_near
  • find_networks_on_ixps_near

Change feed

  • get_events

Growth & trends

  • find_growing_networks
  • get_asn_growth

IXP & facility

  • get_ixp_info
  • get_ixp_members
  • get_facility_info
  • get_facility_members
  • get_facility_events
  • find_near

CDN

  • search_cdn_caches

Traceroutes & analysis

  • search_traceroutes
  • get_traceroute
  • get_my_traceroutes
  • get_asn_traces
  • get_route_heatmap
  • get_country_scoreboard
  • get_tier1_comparison
  • get_tier1_city_view
  • get_asn_connectivity_profile
  • get_common_routes
  • get_boomerang_stats
  • get_asn_routing_quality
  • get_dashboard_summary

Relationships

  • find_co_located
  • find_transit_customers
  • get_org_asns

Sales & prospecting

  • find_prospects
  • find_long_haul_customers
  • find_transport
  • get_cloud_connect_prospects
  • get_prospect_tags
  • get_clients
  • get_search_templates
  • generate_outreach_templates
  • get_asn_lead_score
  • get_lead_reasons
  • get_buy_signals
  • get_rpki_opportunities
  • get_industry_verticals

Intelligence & ranking

  • get_new_networks
  • get_growing_networks
  • get_prefix_growth
  • get_country_benchmark
  • get_top_networks

Domains

  • domain_lookup
  • search_domains
  • get_domain_intel
  • compare_domain_scans

Exports

  • create_export
  • get_export
  • list_exports
  • cancel_export

On-demand measurements

  • create_measurement
  • get_measurement
  • list_measurements
  • cancel_measurement

Your saved data

  • get_watchlist
  • get_alerts
  • get_notifications
  • get_saved_searches
  • get_auto_scout
  • get_favorites
  • get_trace_favorites
  • get_dashboard_pins
  • get_dashboard_digest
  • list_alert_destinations
  • get_alert_deliveries

Org & seller

  • get_org_customers
  • get_org_prospects
  • get_org_performance_targets
  • get_org_assets
  • get_seller_profile
  • get_custom_facility

Actions (write)

  • add_to_watchlist
  • remove_from_watchlist
  • add_ixp_to_watchlist
  • remove_ixp_from_watchlist
  • add_facility_to_watchlist
  • remove_facility_from_watchlist
  • add_org_to_watchlist
  • remove_org_from_watchlist
  • pin_to_dashboard
  • unpin_from_dashboard
  • add_favorite
  • remove_favorite
  • add_trace_favorite
  • remove_trace_favorite
  • report_data_issue
  • list_watch_rule_types
  • create_watch_rule
  • preview_watch_rule
  • list_watch_rules
  • delete_watch_rule
  • set_watch_rule_active
  • update_watch_rule
  • trigger_domain_scan
  • rerun_domain_scan
  • delete_domain_scan
  • tag_prospect
  • untag_prospect
  • undismiss_prospect
  • save_search
  • delete_saved_search
  • create_auto_scout
  • set_auto_scout_frequency
  • delete_auto_scout
  • create_client
  • add_asn_to_client
  • remove_asn_from_client
  • delete_client
  • mark_alerts_read
  • archive_alerts
  • dismiss_notification
  • archive_trace_batch
  • unarchive_trace_batch
  • exclude_trace_batch_from_transit
  • update_seller_profile
  • set_service_area
  • delete_custom_facility
  • update_custom_facility_service_area
  • update_org_asset
  • update_org_customer
  • ack_all_org_customers
  • add_org_member
  • remove_org_member
  • change_org_member_role
  • create_alert_destination
  • update_alert_destination
  • delete_alert_destination
  • test_alert_destination

A deprecated tag marks an old name kept only as an alias - get_asn_peers despite its name returns IXP co-presence, not observed peers; use get_asn_neighbours for BGP adjacency (#259).

Three more tools appear when your account belongs to an organisation, and 10 admin-only tools exist beyond that (not listed above - internal operational tools, not part of the documented public surface). Everything is access-checked server-side, so a tool you cannot use never appears in your list.

Focusing the tool list

160 tools is a lot of context for one conversation. The server publishes curated subsets you can paste into a client that supports a function-name filter (OpenWebUI, for example), generated from the same TOOL_PRESETS the server itself uses:

PresetTools
whois & lookuplookup_asn lookup_ip lookup_prefix get_asn_bogons get_asn_prefixes get_asn_facilities get_asn_ixps search_asns get_facility_members find_near search_allocations get_asn_allocations search_transfers lookup_rir_org
who is in this areafind_networks_near find_transit_near find_upstreams_near find_networks_at_facilities_near find_networks_on_ixps_near find_near lookup_ip get_facility_members get_ixp_members
growth prospectingfind_growing_networks get_asn_growth find_networks_at_facilities_near find_networks_on_ixps_near lookup_asn get_events
saleslookup_asn search_asns find_prospects find_ddos_protected get_lead_reasons get_buy_signals get_top_networks find_transport tag_prospect create_client add_asn_to_client search_dormant_ipv4
network ops / researchlookup_asn lookup_ip get_asn_history get_route_history get_bgp_state get_asn_neighbours get_transit_providers find_upstreams_near search_traceroutes get_route_heatmap get_tier1_comparison get_tier1_city_view get_country_scoreboard compare_asns recommend_transit

Example prompts

Once the connector is live, ask in plain language. These are real questions the tool set answers:

"Look up AS577, then compare it with AS812 and AS6327 - who has the widest IXP footprint?"

uses lookup_asn, compare_asns, get_asn_ixps

"Which networks peer at an exchange within 100 km of Montréal but have no data-centre presence there?"

uses find_networks_on_ixps_near, find_networks_at_facilities_near

"Who does AS13335 buy transit from, and which of those handoffs did we actually see in a traceroute?"

uses get_transit_providers

"Who peers with AS33185, and in which direction?"

uses get_asn_neighbours

"Show me Canadian networks that added the most facilities in the last 90 days, then tag the top three as prospects."

uses find_growing_networks, tag_prospect

"Did AS16509 buy its address space or was it allocated directly? Show any transfers."

uses get_asn_allocations, search_transfers

Tool results carry their own caveats - coverage limits, what an observation does and does not prove - and the assistant will repeat them. That is deliberate: a BGP adjacency is not proof of a commercial relationship, and "not observed" is not "does not exist".

Claude

Claude Code (CLI)

Add the server once, with your API key as a header:

claude mcp add --transport http bgpscout https://bgpscout.io/mcp \
  --header "X-API-Key: YOUR_API_KEY"

# confirm it registered
claude mcp list
claude mcp get bgpscout

The default scope is the current project. Add --scope user to make it available in all your projects, or --scope project to share it with your team through a checked-in .mcp.json. Inside a session, /mcp shows connection status.

Claude Code with OAuth instead of a key

Register the server without a header, then authenticate in the browser - useful if you would rather not store a key in a config file:

claude mcp add --transport http bgpscout https://bgpscout.io/mcp
# then, inside Claude Code:
#   /mcp  →  select "bgpscout"  →  Authenticate

claude.ai and Claude Desktop (custom connector)

  1. Open Settings → Connectors.
  2. Click Add custom connector.
  3. Paste the URL https://bgpscout.io/mcp and click Add.
  4. Click Connect. You are sent to BGPScout to sign in with Google or PeeringDB, then back to Claude with the tools enabled.

Use the OAuth flow here rather than a custom header. Header auth in claude.ai connectors is a gradual rollout and, as of July 2026, headers are not reliably forwarded on every tool call - OAuth is the dependable path. On Team and Enterprise plans an owner adds the connector from Admin settings → Connectors and members then connect to it.

ChatGPT

To connect BGPScout to ChatGPT as an MCP app:

  1. Go to ChatGPT Settings → Apps → Add App.
  2. Set Name to bgpscout.io.
  3. Set MCP Server URL to https://bgpscout.io/mcp.
  4. Set Authentication to OAuth.
  5. Leave Registration method on Dynamic Client Registration. BGPScout implements RFC 7591 at /oauth/register and advertises it in its metadata, so the client registers itself and is issued a client ID automatically - there is no fixed ID to paste.
  6. Only if your client cannot do dynamic registration, register once by hand and use the ID it returns:
    curl -X POST https://bgpscout.io/oauth/register \
      -H 'Content-Type: application/json' \
      -d '{"client_name":"My Client","redirect_uris":["https://example.com/callback"]}'
    The response contains the client_id to use below. Client secrets are not issued: this is a public client, so token_endpoint_auth_method is none and PKCE (S256) is mandatory.
  7. The remaining fields:
    OAuth Client IDFrom dynamic registration, or the client_id returned above
    OAuth Client SecretLeave empty
    Token endpoint auth methodnone
    Auth URLhttps://bgpscout.io/oauth/authorize
    Token URLhttps://bgpscout.io/oauth/token
  8. Check "I understand and want to continue", then click Create.

The first time you use the BGPScout app in ChatGPT you are redirected to BGPScout to log in with Google or PeeringDB. After authorising, you land back in ChatGPT with the MCP tools available.

OAuth endpoints reference

PurposeURL
Authorizationhttps://bgpscout.io/oauth/authorize
Token exchangehttps://bgpscout.io/oauth/token
Dynamic registrationhttps://bgpscout.io/oauth/register
Protected resource metadatahttps://bgpscout.io/.well-known/oauth-protected-resource
Authorization server metadatahttps://bgpscout.io/.well-known/oauth-authorization-server

Grant types are authorization_code and refresh_token. PKCE is mandatory - the authorization endpoint requires a code_challenge with code_challenge_method=S256 and rejects anything else. Before a code is issued you see an explicit consent screen naming the application and the scopes it asked for; nothing is granted silently.

Cursor

Cursor reads .mcp.json. Add BGPScout with an API key header - the same block works in any config-file client:

{
  "mcpServers": {
    "bgpscout": {
      "type": "http",
      "url": "https://bgpscout.io/mcp",
      "headers": {
        "X-API-Key": "YOUR_API_KEY"
      }
    }
  }
}

Get the key from Account Settings. Cursor does not do the OAuth flow that ChatGPT and claude.ai use, so the header is the route here.

OpenWebUI

Add an OpenAPI tool server pointing at https://bgpscout.io/mcp (spec at /mcp/openapi.json), with auth set to Bearer and your API key as the token.

160 tools is a lot of context for one conversation. OpenWebUI supports a function-name filter, so paste one of the curated presets from the Tool catalogue into it to narrow what loads.

EndpointTraceroute statistics

GET/api/traceroutes/statsPublic

Returns aggregate statistics about all collected traceroutes, plus a breakdown of the source countries they came from.

Example request

curl https://bgpscout.io/api/traceroutes/stats

Example response

Live values, captured when this page was rendered.

{
  "success": true,
  "stats": {
    "total_traces": 283361,
    "source_countries": 146,
    "unique_destinations": 9036,
    "unique_source_asns": 3654,
    "boomerang_count": "482",
    "leaves_country_count": "50450",
    "leaves_canada_count": "32088",
    "earliest_trace": "2026-04-02T14:24:53.000Z",
    "latest_trace": "2026-08-29T09:15:10.000Z"
  },
  "top_source_countries": [
    { "source_country": "CA", "trace_count": 75260 },
    { "source_country": "US", "trace_count": 24103 }
  ]
}

Counts that come from SQL aggregates are serialised as strings, so parse them as numbers rather than assuming a JSON number type.

leaves_canada_count is a legacy, Canada-specific field kept under its old name for backward compatibility (#363) - it counts CA-sourced traces that left their own source country, not traces that merely touched Canada as the underlying leaves_canada column once did. leaves_country_count is the country-agnostic figure, computed the same way for every country, and is the one to use for anything not specifically about Canada.

EndpointSubmit traceroutes

POST/api/traceroutes/submitRequires API key

Submit one or more traceroute results for storage and analysis.

Maximum 50 traces per request. The JSON body limit is 1 MB - split large batches across calls.

Headers

NameRequiredDescription
X-API-KeyYesYour API key
Content-TypeYesapplication/json

Request body

{
  "traces": [
    {
      "host": "example.com",
      "dest_ip": "93.184.216.34",
      "source_ip": "192.168.1.1",
      "source_country": "CA",
      "platform": "linux",
      "tool": "mtr",
      "completed": true,
      "hops": [
        {
          "hop_number": 1,
          "ip": "192.168.1.1",
          "hostname": "gateway.local",
          "rtt_avg": 1.2,
          "rtt_min": 0.8,
          "rtt_max": 2.1,
          "loss_pct": 0
        },
        {
          "hop_number": 2,
          "ip": "10.0.0.1",
          "rtt_avg": 5.4
        }
      ]
    }
  ]
}

Hop fields

FieldTypeRequiredDescription
hop_numberintegerYesTTL hop position (1-64)
ipstringNoIP address of the hop (null for timeouts)
hostnamestringNoReverse DNS hostname
rtt_avgfloatNoAverage round-trip time in ms
rtt_minfloatNoMinimum RTT in ms
rtt_maxfloatNoMaximum RTT in ms
loss_pctfloatNoPacket loss percentage (0-100)

Example response

{
  "success": true,
  "trace_ids": [1042, 1043],
  "count": 2
}

Error response

{
  "success": false,
  "error": "Validation failed",
  "details": [
    { "trace_index": 0, "errors": ["Invalid dest_ip format"] }
  ]
}

EndpointASN information

GET/api/asn/:asnPublic

Returns core information about an ASN, including peering quality and route scoring. For observed BGP adjacency (who this ASN is upstream/downstream of), see GET /api/asn/:asn/neighbours below.

Parameters

NameTypeDescription
:asnintegerThe AS number to look up (e.g. 577)

Example request

curl https://bgpscout.io/api/asn/577

Example response

{
  "asn": 577,
  "name": "Bell Canada Backbone",
  "country": "CA",
  "rir": "ARIN",
  "traffic_level": null,
  "network_type": "NSP",
  "on_ix": true,
  "peering_quality": {
    "crossing_rate": 6.33,
    "quality_tag": "good_peering"
  },
  "route_score": {
    "quality": 5.4,
    "route": 7.3,
    "overall": 6.4
  },
  "url": "https://bgpscout.io/asns/577"
}

route_score values are on a 0-10 scale. crossing_rate is the percentage of observed traces that left the country. peering_quality, route_score and traffic_level are null when we have not observed enough data for that network - that is a real answer, not an error. An unknown AS number returns 404 with {"error": "ASN not found"}.

EndpointASN adjacency (BGP neighbours)

GET/api/asn/:asn/neighboursPublic

Every ASN observed adjacent to this one in BGP AS_PATH data, in both directions - what the source historically used for ASN classification calls its #peers tab, while bgp.he.net shows on every ASN page. Same shape and same field names as the MCP tool get_asn_neighbours.

Example request

curl https://bgpscout.io/api/asn/33185/neighbours

Example response

Abridged, against the live table on 2026-08-24 (27 neighbours for AS33185). The neighbour arrays are trimmed here; the counts are real.

{
  "asn": 33185,
  "known": true,
  "neighbour_count": 27,
  "seen_upstream_ward_count": 5,
  "seen_downstream_ward_count": 22,
  "returned": 27,
  "limit": 100,
  "truncated": false,
  "seen_upstream_ward": [
    { "asn": 1299, "name": "Arelion (fka. Telia Carrier)", "country": "SE", "observations": 251, "peer_count": 9 },
    { "asn": 3257,  "name": "GTT Communications Inc.",     "country": "US", "observations": 151, "peer_count": 7 },
    { "asn": 6939,  "name": "Hurricane Electric LLC",       "country": "US", "observations": 125, "peer_count": 9 },
    { "asn": 174,   "name": "Cogent Communications, LLC",   "country": "US", "observations": 95,  "peer_count": 3 }
  ],
  "seen_downstream_ward": [
    { "asn": 398292, "name": "Babish Telecom", "country": "CA", "observations": 102, "peer_count": 17 }
  ],
  "observed_from": {
    "collectors": ["route-views.chicago", "route-views.eqix", "...", "rrc24"],
    "collector_count": 12,
    "collectors_unreachable_that_run": [],
    "built_at": "2026-08-22",
    "min_collector_peers_required": 2
  },
  "notes": {
    "relationship": "AS_PATH does not label relationships. An adjacency may be transit OR settlement-free peering OR a sibling, and it is not proof that money changes hands.",
    "direction": "\"seen_upstream_ward\" means the neighbour was observed further from the origin in observed paths, and \"seen_downstream_ward\" nearer to it. That is an observation, not proven hierarchy - leaks and siblings break it.",
    "coverage": "Observed from 12 BGP route collectors (8 RouteViews + 4 RIPE RIS) on 2026-08-22: route-views.chicago, route-views.eqix, ... rrc24.",
    "coverage_limit": "This is a FLOOR, not a peer census, and adding collectors does not lift the ceiling much ..."
  }
}

Fields

FieldDescription
knownfalse when this ASN has never been observed adjacent to anything - that is different from true with empty arrays, which means we looked and found none within the requested limit (#67: absent is not zero).
neighbour_countThe full total for the ASN, read from the precomputed asn_peer_count table - not the number of rows in the arrays below. null when known is false, never 0. Until 2026-08-24 this field was the page size, so a 100-row page of a 1,300-neighbour network reported 200; refetch anything cached from before then.
returned, truncatedreturned is how many rows the arrays actually hold; truncated is true when that is fewer than neighbour_count. Raise ?limit= (max 500) before treating the arrays as the whole set.
observed_fromWhich route collectors produced the generation of the table you are being served, when it was built, how many distinct collector peers a pair needed before it was kept, and any configured collector that was unreachable on that run. Read from the build's own provenance record rather than written into the prose, so it cannot go stale. null only if that record is unreadable.
observationsHow many times this specific adjacency was seen across collected AS_PATHs.
peer_countHow many distinct collector-facing peers reported seeing this adjacency - a rough proxy for how well-corroborated it is, independent of raw observation volume.

The caveats matter more here than on most endpoints, because a longer neighbour list reads as a stronger claim than it is. AS_PATH does not label relationships - an adjacency may be transit, settlement-free peering, or a sibling, not proof money changes hands. Direction is observation, not hierarchy: "upstream-ward" means seen further from the origin, and leaks or sibling ASNs break that assumption. A short or empty result is a statement about collector visibility, not about the network - and that does not go away by adding collectors. An MRT RIB dump carries the best path per prefix per collector peer, so an adjacency that is never the best path anywhere, which is the normal case for settlement-free peering across an exchange route server, is absent from every collector. Measured 2026-08-24 against our own PeeringDB data: of the 2,077 ASNs that are route-server peers at exactly one exchange carrying 200+ other route-server peers, the mean count here is 6.69, and 30% show two or fewer. These are floors.

Data-quality check run 2026-08-21 against the then-live single-collector asn_adjacency table (155,782 rows, 79,484 distinct ASNs, matching asn_peer_count exactly; the twelve-collector build of 2026-08-22 took it to 221,194 rows and 87,177 ASNs): 60 rows (0.04%) name an ASN outside the current RIR-delegated range, almost entirely the 16-bit private-use block (64512-65534) leaking into real AS_PATHs from operator misconfiguration - the same phenomenon #197 documented, not a parsing bug. Clean enough to publish; still worth knowing an undelegated ASN can legitimately appear here.

EndpointEmbeddable ASN widget

GET/api/embed/asn/:asnPublic

Returns a self-contained HTML card for an ASN that can be embedded in an iframe. It shows the ASN name, country, peering-quality badge, route score, and a link back to BGPScout.

Usage

<iframe src="https://bgpscout.io/api/embed/asn/577"
        width="320" height="200"
        frameborder="0"></iframe>

AlertsAlert delivery webhooks

BGPScout can POST an alert to an HTTPS endpoint you own, so a watch rule reaches your NOC without anyone opening BGPScout. Every request is signed with a per-destination HMAC-SHA256 secret. This section is the receiver's contract: the payload schema, the headers, and the exact verification recipe.

Destinations are managed through the MCP tools list_alert_destinations, create_alert_destination, update_alert_destination, test_alert_destination, delete_alert_destination and get_alert_deliveries. The signing secret is returned once, by the call that creates the destination, and is never readable again. Losing it means rotating it (update_alert_destination with rotate_secret), which is a deliberate one-way door.

Requirements for your endpoint

WhatValue
Scheme and porthttps:// on port 443 only. Plain HTTP, a non-443 port, and URLs carrying embedded credentials are refused when the destination is saved and again on every single delivery.
AddressMust resolve to a public address. Loopback, RFC1918, carrier-grade NAT, link-local, unique-local, multicast, documentation and cloud-metadata ranges are rejected for both IPv4 and IPv6, including IPv4-mapped and NAT64-wrapped forms. The name is re-resolved on every attempt and the connection is made to the address that was validated, so a DNS answer that changes after validation cannot be used.
RedirectsNever followed. A 3xx is recorded as a failed delivery (redirect_rejected). To move your endpoint, update the destination URL.
ResponseAny 2xx means delivered. Reply quickly and do the work asynchronously: the request times out after 10 seconds, and at most 64 KB of your response body is read.
RetriesA 5xx or 429 is retried with exponential backoff plus jitter (1 minute doubling to a 30 minute cap). Any other 4xx is also retried. After 5 attempts the delivery stops permanently and is marked dead, visible to the destination owner through get_alert_deliveries.

Payload

The body is JSON, version-stamped by schema_version. Fields are added compatibly within a version; a receiver should ignore fields it does not recognise.

{
  "schema_version": 1,
  "event_id": "watchlist_alert:13112",
  "event_type": "ixp_joined",
  "occurred_at": "2026-08-26T02:14:07.000Z",
  "severity": null,
  "subject": { "type": "asn", "id": "64500" },
  "summary": "AS64500 joined DE-CIX Frankfurt (seen 2026-08-25) [rule: my rule]",
  "facts": {},
  "evidence": [],
  "links": { "reference": "/asns/64500" },
  "test": false
}
FieldMeaning
schema_versionInteger. 1 today. A receiver should reject a major version it does not understand rather than guess.
event_idStable and unique per alert: watchlist_alert:<id> for a real alert, test:<random> for a synthetic test. Delivery is at-least-once by design, so dedupe on this value.
event_typeThe alert type, for example ixp_joined, ixp_left, facility_added, asn_registered. test.synthetic_event for a test send.
occurred_atISO 8601 UTC. When the alert was created, not when it was delivered.
severityCurrently null on every real alert. BGPScout does not yet record a severity on an alert, and a guessed value would be worse than an honest one. Test sends carry "info". Do not build a filter that requires this field to be non-null.
subjectWhat the alert is about: type is one of asn, ixp, facility, org, prefix, cdn, and id is the identifier within that type.
summaryHuman-readable one-line description, including which rule fired.
facts / evidenceReserved for structured detail. Present and empty today; they will not change type.
linksreference is a path on bgpscout.io for the subject, when one can be named honestly. An empty object means there is no page to link, not an error.
testtrue only for test_alert_destination. A test send never creates a real alert and never touches the delivery queue. Treat a payload with test: true as a connectivity check, never as an incident.

Headers

HeaderValue
BGPScout-Event-IdSame value as event_id in the body. The idempotency key.
BGPScout-Event-TypeSame value as event_type in the body.
BGPScout-TimestampUnix seconds at the moment of signing. Part of what is signed.
BGPScout-Signaturev1= followed by the hex HMAC-SHA256. The v1= prefix is the signature scheme version, not the payload version.
Content-Typeapplication/json
User-AgentBGPScout-Webhooks/1.0

Verifying the signature

  1. Read the BGPScout-Timestamp header and the raw request body exactly as received. Do not re-serialise the parsed JSON: key order and whitespace are part of what was signed.
  2. Reject the request if abs(now - timestamp) exceeds your replay window. We recommend 300 seconds. This is what makes a captured request unreplayable, because the signature covers the timestamp.
  3. Compute HMAC-SHA256(secret, timestamp + "." + rawBody) and hex-encode it.
  4. Compare it against the value after v1= using a constant-time comparison. Never use a plain string equality on a secret-derived value.
  5. During a secret rotation the sender may sign with either the current or the previous secret for a 24 hour overlap. A receiver holding both should accept either.
// Node.js reference implementation. Note express.raw:
// a JSON body parser would destroy the exact bytes that were signed.
const crypto = require('crypto');

app.post('/bgpscout-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const ts  = req.get('BGPScout-Timestamp');
  const sig = req.get('BGPScout-Signature') || '';
  const raw = req.body;                      // a Buffer, unparsed

  if (Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > 300) {
    return res.status(400).send('stale timestamp');
  }

  const expected = crypto.createHmac('sha256', YOUR_SECRET)
                         .update(ts + '.' + raw.toString('utf8'))
                         .digest('hex');
  const given = Buffer.from(sig.replace(/^v1=/, ''), 'hex');
  const want  = Buffer.from(expected, 'hex');
  if (given.length !== want.length || !crypto.timingSafeEqual(given, want)) {
    return res.status(401).send('bad signature');
  }

  // Dedupe on the event id: delivery is at-least-once.
  if (alreadyHandled(req.get('BGPScout-Event-Id'))) return res.status(200).end();

  res.status(200).end();          // acknowledge FIRST, then do the work
  handleEvent(JSON.parse(raw));
});

A destination is not armed by the act of saving a URL. It is created disabled, and it can only be enabled after test_alert_destination has produced a real 2xx from your endpoint. Changing the URL clears that verification and disables the destination again.

ToolsMTR client

The BGPScout MTR client collects traceroute data and submits it to the API automatically.

Download

Download latest release

Installation

# Linux / macOS
chmod +x bgpscout-mtr
sudo mv bgpscout-mtr /usr/local/bin/

# Verify installation
bgpscout-mtr --version

Configuration

# Set your API key (from /account)
bgpscout-mtr config --api-key YOUR_API_KEY

# Optionally set your source ASN
bgpscout-mtr config --source-asn 64500

Usage

# Run a single trace
bgpscout-mtr trace example.com

# Run against Tranco top sites (batch mode)
bgpscout-mtr batch --targets tranco-top100.txt

# Run with automatic submission
bgpscout-mtr trace --submit example.com

# Schedule recurring traces (cron-friendly)
bgpscout-mtr batch --targets targets.txt --submit --quiet

Example output

$ bgpscout-mtr trace --submit google.ca
Tracing google.ca (142.250.80.67)...
 1  gateway.local (192.168.1.1)    1.2ms
 2  10.0.0.1                       5.4ms
 3  isp-core.example.net           12.1ms
 ...
Submitted trace ID: 1042