{
  "openapi": "3.1.1",
  "info": {
    "title": "Signalhouse Route Lookup & Packet Path Simulator (longest prefix match) API",
    "version": "1.0.0",
    "summary": "Which route wins? Longest prefix match, distances, ECMP — or a hop-by-hop packet walk.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://signalhouseroutelookup.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Which route wins? Longest prefix match, distances, ECMP — or a hop-by-hop packet walk.",
        "description": "Paste a routing table (connected, static, null, OSPF, BGP … routes with next hops, administrative distances and metrics) and destination addresses: the simulator builds the forwarding table like a router — best distance, then metric, equal-cost paths, recursive next hops, interfaces that are down — and explains for every destination which route wins by longest prefix match and why the others lose. With a Signalhouse Network lab topology it walks a packet hop by hop and finds blackholes, forwarding loops, MTU drops and asymmetric return paths.\n\nMethodology:\n- Each route is first checked for usability: its interface must be up, a next hop without an interface must resolve through another route (recursively, at most 8 levels, never through its own prefix) and a distance of 255 means unusable.\n- Per prefix the router installs the usable route with the lowest administrative distance, then the lowest metric; several routes with the same best distance and metric are installed together as equal-cost paths (ECMP). The others stay as backups — a static route with a higher distance is a “floating” backup.\n- A lookup takes the longest installed prefix that contains the destination (RFC 1812, RFC 4632). Connected prefixes are delivered with ARP or Neighbor Discovery, local addresses are received, discard routes drop the packet, no match means ICMP destination unreachable.\n- Default distances follow Cisco IOS (connected 0, static 1, eBGP 20, EIGRP 90, OSPF 110, IS-IS 115, RIP 120, iBGP 200). Other vendors use other defaults — set the distance column to match your platform.\n- Topology mode repeats the lookup on every router of the path, decrements the TTL, resolves the next hop on the egress segment (through switches), checks the MTU (DF set: drop with “fragmentation needed”; IPv6: “Packet Too Big”), detects loops by a second visit of the same router and compares the return path link by link.\n\nA teaching and planning simulation: it does not model policy routing, VRFs, route maps, NAT, ACLs or dynamic protocol timers. The link-state protocol of topology mode is a simplified single-area SPF.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "signalhouse_route_lookup",
        "x-owner-app": "signalhouse",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignalhouseRouteLookupInput"
              },
              "examples": {
                "example1": {
                  "summary": "Longest prefix match, ECMP, a floating backup and recursive next hops",
                  "value": {
                    "routes": [
                      {
                        "prefix": "0.0.0.0/0",
                        "nextHop": "203.0.113.1",
                        "protocol": "static",
                        "name": "Default route"
                      },
                      {
                        "prefix": "203.0.113.0/24",
                        "interface": "eth0",
                        "protocol": "connected"
                      },
                      {
                        "prefix": "192.0.2.0/24",
                        "interface": "eth1",
                        "protocol": "connected"
                      },
                      {
                        "prefix": "10.0.0.0/8",
                        "nextHop": "192.0.2.1",
                        "protocol": "ospf",
                        "metric": 20
                      },
                      {
                        "prefix": "10.0.0.0/8",
                        "nextHop": "192.0.2.5",
                        "protocol": "ospf",
                        "metric": 20
                      },
                      {
                        "prefix": "10.1.0.0/16",
                        "nextHop": "192.0.2.9",
                        "protocol": "static"
                      },
                      {
                        "prefix": "10.1.0.0/16",
                        "nextHop": "192.0.2.1",
                        "protocol": "ospf",
                        "metric": 5
                      },
                      {
                        "prefix": "10.1.2.0/24",
                        "nextHop": "10.1.255.1",
                        "protocol": "static",
                        "name": "Recursive next hop"
                      }
                    ],
                    "destinations": "10.1.2.3\n10.200.0.1\n198.51.100.7"
                  }
                },
                "example2": {
                  "summary": "RFC 4632 rule 2: the child network’s discard route stops a loop",
                  "value": {
                    "routes": [
                      {
                        "prefix": "0.0.0.0/0",
                        "nextHop": "198.51.100.1",
                        "protocol": "static",
                        "name": "Default to the parent"
                      },
                      {
                        "prefix": "198.51.100.0/30",
                        "interface": "eth0",
                        "protocol": "connected"
                      },
                      {
                        "prefix": "192.168.64.0/24",
                        "interface": "eth1",
                        "protocol": "connected"
                      },
                      {
                        "prefix": "192.168.65.0/24",
                        "interface": "eth2",
                        "protocol": "connected",
                        "name": "LAN that lost its link"
                      },
                      {
                        "prefix": "192.168.64.0/19",
                        "interface": "Null0",
                        "protocol": "blackhole",
                        "name": "Discard route for the aggregate"
                      }
                    ],
                    "destinations": "192.168.65.1\n192.168.64.20\n8.8.8.8",
                    "interfacesDown": "eth2"
                  }
                },
                "example3": {
                  "summary": "IPv6: connected /64, default route and a more specific static route",
                  "value": {
                    "routes": [
                      {
                        "prefix": "::/0",
                        "nextHop": "fe80::1",
                        "interface": "eth0",
                        "protocol": "static"
                      },
                      {
                        "prefix": "2001:db8:1::/64",
                        "interface": "eth1",
                        "protocol": "connected"
                      },
                      {
                        "prefix": "2001:db8:1::1/128",
                        "interface": "eth1",
                        "protocol": "local"
                      },
                      {
                        "prefix": "2001:db8:100::/48",
                        "nextHop": "2001:db8:1::fe",
                        "protocol": "static"
                      },
                      {
                        "prefix": "2001:db8:100::/48",
                        "nextHop": "2001:db8:1::fd",
                        "protocol": "ebgp"
                      }
                    ],
                    "destinations": "2001:db8:100:5::1\n2001:db8:1::1\n2001:db8:ffff::1"
                  }
                },
                "example4": {
                  "summary": "Packet walk through the Network lab example “aggregate without a null route”",
                  "value": {
                    "routes": [],
                    "destinations": "",
                    "topology": "{\"nodes\":[{\"id\":\"net\",\"name\":\"Internet host\",\"kind\":\"host\",\"x\":80,\"y\":300,\"interfaces\":[{\"name\":\"eth0\",\"addresses\":[\"203.0.113.10/24\"]}],\"routes\":[{\"prefix\":\"0.0.0.0/0\",\"nextHop\":\"203.0.113.1\",\"name\":\"Default gateway\"}]},{\"id\":\"parent\",\"name\":\"Parent\",\"kind\":\"router\",\"x\":330,\"y\":300,\"interfaces\":[{\"name\":\"eth0\",\"addresses\":[\"203.0.113.1/24\"]},{\"name\":\"eth1\",\"addresses\":[\"198.51.100.1/30\"]}],\"routes\":[{\"prefix\":\"192.168.64.0/19\",\"nextHop\":\"198.51.100.2\",\"name\":\"Aggregate announced by Child\"}]},{\"id\":\"child\",\"name\":\"Child\",\"kind\":\"router\",\"x\":620,\"y\":300,\"interfaces\":[{\"name\":\"eth0\",\"addresses\":[\"198.51.100.2/30\"]},{\"name\":\"eth1\",\"addresses\":[\"192.168.64.1/24\"]},{\"name\":\"eth2\",\"addresses\":[\"192.168.65.1/24\"]}],\"routes\":[{\"prefix\":\"0.0.0.0/0\",\"nextHop\":\"198.51.100.1\",\"name\":\"Default to Parent\"},{\"prefix\":\"192.168.64.0/19\",\"interface\":\"Null0\",\"protocol\":\"blackhole\",\"name\":\"Discard route for the aggregate (RFC 4632 rule 2)\",\"disabled\":true}]},{\"id\":\"h64\",\"name\":\"Host 64\",\"kind\":\"host\",\"x\":880,\"y\":160,\"interfaces\":[{\"name\":\"eth0\",\"addresses\":[\"192.168.64.10/24\"]}],\"routes\":[{\"prefix\":\"0.0.0.0/0\",\"nextHop\":\"192.168.64.1\",\"name\":\"Default gateway\"}]},{\"id\":\"h65\",\"name\":\"Host 65\",\"kind\":\"host\",\"x\":880,\"y\":440,\"interfaces\":[{\"name\":\"eth0\",\"addresses\":[\"192.168.65.10/24\"]}],\"routes\":[{\"prefix\":\"0.0.0.0/0\",\"nextHop\":\"192.168.65.1\",\"name\":\"Default gateway\"}]}],\"links\":[{\"id\":\"l1\",\"a\":\"net\",\"aIf\":\"eth0\",\"b\":\"parent\",\"bIf\":\"eth0\",\"up\":true},{\"id\":\"l2\",\"a\":\"parent\",\"aIf\":\"eth1\",\"b\":\"child\",\"bIf\":\"eth0\",\"up\":true,\"label\":\"Parent – Child\"},{\"id\":\"l3\",\"a\":\"child\",\"aIf\":\"eth1\",\"b\":\"h64\",\"bIf\":\"eth0\",\"up\":true},{\"id\":\"l4\",\"a\":\"child\",\"aIf\":\"eth2\",\"b\":\"h65\",\"bIf\":\"eth0\",\"up\":false,\"label\":\"Lost LAN 192.168.65.0/24\"}]}",
                    "from": "net",
                    "to": "192.168.65.10",
                    "size": 1500
                  }
                },
                "example5": {
                  "summary": "Packet walk with an asymmetric return path",
                  "value": {
                    "routes": [],
                    "destinations": "",
                    "topology": "{\"nodes\":[{\"id\":\"client\",\"name\":\"Client\",\"kind\":\"host\",\"x\":80,\"y\":300,\"interfaces\":[{\"name\":\"eth0\",\"addresses\":[\"172.16.1.10/24\"]}],\"routes\":[{\"prefix\":\"0.0.0.0/0\",\"nextHop\":\"172.16.1.1\",\"name\":\"Default gateway\"}]},{\"id\":\"r1\",\"name\":\"R1\",\"kind\":\"router\",\"x\":330,\"y\":300,\"interfaces\":[{\"name\":\"eth0\",\"addresses\":[\"172.16.1.1/24\"]},{\"name\":\"fibre\",\"addresses\":[\"10.0.1.1/30\"]},{\"name\":\"radio\",\"addresses\":[\"10.0.2.1/30\"]}],\"routes\":[{\"prefix\":\"172.16.2.0/24\",\"nextHop\":\"10.0.1.2\",\"name\":\"Server LAN over fibre\"},{\"prefix\":\"172.16.2.0/24\",\"nextHop\":\"10.0.2.2\",\"distance\":10,\"name\":\"Server LAN over radio (backup)\"}]},{\"id\":\"r2\",\"name\":\"R2\",\"kind\":\"router\",\"x\":640,\"y\":300,\"interfaces\":[{\"name\":\"eth0\",\"addresses\":[\"172.16.2.1/24\"]},{\"name\":\"fibre\",\"addresses\":[\"10.0.1.2/30\"]},{\"name\":\"radio\",\"addresses\":[\"10.0.2.2/30\"]}],\"routes\":[{\"prefix\":\"172.16.1.0/24\",\"nextHop\":\"10.0.2.1\",\"name\":\"Client LAN over radio\"}]},{\"id\":\"server\",\"name\":\"Server\",\"kind\":\"host\",\"x\":900,\"y\":300,\"interfaces\":[{\"name\":\"eth0\",\"addresses\":[\"172.16.2.20/24\"]}],\"routes\":[{\"prefix\":\"0.0.0.0/0\",\"nextHop\":\"172.16.2.1\",\"name\":\"Default gateway\"}]}],\"links\":[{\"id\":\"l1\",\"a\":\"client\",\"aIf\":\"eth0\",\"b\":\"r1\",\"bIf\":\"eth0\",\"up\":true},{\"id\":\"l2\",\"a\":\"r1\",\"aIf\":\"fibre\",\"b\":\"r2\",\"bIf\":\"fibre\",\"up\":true,\"label\":\"Fibre\",\"cost\":1},{\"id\":\"l3\",\"a\":\"r1\",\"aIf\":\"radio\",\"b\":\"r2\",\"bIf\":\"radio\",\"up\":true,\"label\":\"Radio\",\"cost\":50,\"mtu\":1400},{\"id\":\"l4\",\"a\":\"r2\",\"aIf\":\"eth0\",\"b\":\"server\",\"bIf\":\"eth0\",\"up\":true}]}",
                    "from": "client",
                    "to": "172.16.2.20",
                    "size": 1400
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignalhouseRouteLookupResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "SignalhouseRouteLookupInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "routes": {
            "title": "Routing table",
            "description": "One route per row. Protocol: connected, local, static, blackhole (null route), ospf, rip, eigrp, isis, ebgp or ibgp. Leave distance empty for the default (connected 0, static 1, eBGP 20, OSPF 110, RIP 120, iBGP 200).",
            "type": "array",
            "maxItems": 2000,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "prefix": {
                  "title": "Prefix",
                  "type": "string",
                  "maxLength": 60
                },
                "nextHop": {
                  "title": "Next hop",
                  "type": "string",
                  "maxLength": 60
                },
                "interface": {
                  "title": "Interface",
                  "type": "string",
                  "maxLength": 40
                },
                "protocol": {
                  "title": "Protocol",
                  "type": "string",
                  "enum": [
                    "connected",
                    "local",
                    "static",
                    "blackhole",
                    "ospf",
                    "rip",
                    "eigrp",
                    "isis",
                    "ebgp",
                    "ibgp"
                  ],
                  "default": "static"
                },
                "distance": {
                  "title": "Distance",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 255
                },
                "metric": {
                  "title": "Metric",
                  "type": "integer",
                  "minimum": 0
                },
                "name": {
                  "title": "Description",
                  "type": "string",
                  "maxLength": 80
                }
              },
              "required": []
            },
            "default": [
              {
                "prefix": "0.0.0.0/0",
                "nextHop": "203.0.113.1",
                "protocol": "static",
                "name": "Default route"
              },
              {
                "prefix": "203.0.113.0/24",
                "interface": "eth0",
                "protocol": "connected"
              },
              {
                "prefix": "192.0.2.0/24",
                "interface": "eth1",
                "protocol": "connected"
              },
              {
                "prefix": "10.0.0.0/8",
                "nextHop": "192.0.2.1",
                "protocol": "ospf",
                "metric": 20
              },
              {
                "prefix": "10.1.0.0/16",
                "nextHop": "192.0.2.9",
                "protocol": "static"
              }
            ]
          },
          "destinations": {
            "title": "Destinations (one per line)",
            "type": "string",
            "maxLength": 8000,
            "default": "10.1.2.3\n10.200.0.1\n198.51.100.7"
          },
          "interfacesDown": {
            "title": "Interfaces that are down (optional)",
            "description": "Comma-separated, e.g. eth1. Their connected routes disappear and routes through them stop working.",
            "type": "string",
            "maxLength": 400
          },
          "topology": {
            "title": "Network lab topology JSON (optional)",
            "description": "Instead of a table: a topology exported from the Signalhouse Network lab ({ nodes, links }). Then name the start device and the destination below.",
            "type": "string",
            "maxLength": 400000
          },
          "from": {
            "title": "Packet from (device id or name)",
            "type": "string",
            "maxLength": 60
          },
          "to": {
            "title": "Packet to (address or device)",
            "type": "string",
            "maxLength": 60
          },
          "size": {
            "title": "Packet size",
            "type": "integer",
            "minimum": 20,
            "maximum": 65535,
            "default": 1500,
            "x-unit": "bytes"
          },
          "df": {
            "title": "“Don’t Fragment” set (IPv4)",
            "type": "boolean",
            "default": true
          }
        },
        "required": []
      },
      "SignalhouseRouteLookupResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "mode",
              "table",
              "path"
            ],
            "properties": {
              "mode": {
                "enum": [
                  "table",
                  "topology"
                ]
              },
              "table": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Table mode: every route with its status (installed, ecmp, backup, invalid) and reason, the number of installed prefixes, and per destination the action, matched route, paths, candidates by prefix length, routes that lost and the reasoning in sentences."
              },
              "path": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Topology mode: summary, symmetric (null when unknown), forward and return walk (status, reason, message, source/destination addresses, hops with the matched route, egress interface, gateway, next device, TTL, MTU) and lab issues."
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}