Consul’s catalog API doesn’t actually perform service discovery; it exposes the results of Consul’s internal service discovery process.

Let’s see it in action. Imagine you have two instances of a service called web-app running, and Consul knows about them. You’d typically interact with the Consul API via curl or a client library.

curl http://localhost:8500/v1/catalog/service/web-app

This request hits the Consul agent’s HTTP API, which is usually running on port 8500. The /v1/catalog/service/web-app endpoint tells Consul: "Show me all the registered instances for the service named web-app."

The output you’d get is a JSON array, looking something like this:

[
  {
    "ID": "web-app-12345",
    "Node": "node-1",
    "Address": "192.168.1.10",
    "ServiceID": "web-app-12345",
    "ServiceName": "web-app",
    "ServiceTags": ["production", "api"],
    "ServicePort": 8080,
    "ServiceAddress": "",
    "ServiceEnableTagOverride": false,
    "Datacenter": "dc1",
    "ServiceMeta": {},
    "ServiceHealthStatus": "passing",
    "ServiceVPCID": "",
    "NodeMeta": {},
    "NodeVPCID": ""
  },
  {
    "ID": "web-app-67890",
    "Node": "node-2",
    "Address": "192.168.1.11",
    "ServiceID": "web-app-67890",
    "ServiceName": "web-app",
    "ServiceTags": ["production", "frontend"],
    "ServicePort": 8080,
    "ServiceAddress": "",
    "ServiceEnableTagOverride": false,
    "Datacenter": "dc1",
    "ServiceMeta": {},
    "ServiceHealthStatus": "passing",
    "ServiceVPCID": "",
    "NodeMeta": {},
    "NodeVPCID": ""
  }
]

This JSON payload is the raw data Consul has about your web-app service instances. Each object in the array represents a single instance. You see its Node (where it’s running), its Address, ServicePort, and crucially, any ServiceTags associated with it. This is the foundation for how applications find each other.

The problem this solves is the brittle nature of hardcoding service locations. Before systems like Consul, you’d have configuration files or environment variables pointing to specific IP addresses and ports. When a service scaled, moved, or failed, you’d have to update all its consumers. Consul, through its catalog API, provides a dynamic registry. Applications query this registry to find healthy instances of the services they depend on.

Internally, Consul agents maintain a local copy of the service catalog. When a service registers (either via its own agent or an external process), the agent updates its local state and gossips this information to other agents in the cluster. The catalog API is just a window into this distributed state. You can query any Consul agent, and it will respond with the aggregated catalog information it has learned from the cluster.

The key levers you control are registration and querying. Services register themselves with Consul, providing their name, address, port, and optional tags. Consumers then query the catalog API using the service name and can filter by tags to find specific instances. For example, to find only web-app instances tagged with api:

curl "http://localhost:8500/v1/catalog/service/web-app?tag=api"

This would return only the web-app instance running on node-1 in our previous example, assuming it had the api tag.

Consul’s catalog API provides several query parameters for fine-grained control. You can filter by datacenter (dc=dc1), node (node=node-1), or even query for services that are not healthy (passing=false). The near parameter is particularly useful for discovering services located close to a specific node, which can be important for latency-sensitive applications.

When you query the catalog API, you are inherently querying a potentially stale view of the service registry. Consul uses a gossip protocol, which means updates propagate eventually. The API response includes a X-Consul-Index header. This index is a monotonically increasing number representing the version of the catalog. You can use this index for blocking queries: if you make a request with a wait parameter and a specific index, Consul will hold the connection open until the catalog changes or the timeout is reached. This is how clients can efficiently watch for service discovery changes without constant polling.

The most surprising thing about the catalog API is how it abstracts away the underlying network topology and service health. You ask for "the web service" and get back a list of healthy endpoints, regardless of whether they are on VMs, containers, or bare metal, and without needing to know their IP addresses beforehand. The responsibility for health checking is delegated to Consul’s health checking system, and the catalog API simply reflects the results of those checks.

You can also query the catalog for nodes themselves, not just services. This allows you to discover all the machines participating in your Consul cluster and their associated metadata.

The next step after querying the catalog API is to use that information to actually establish a connection to one of the discovered service instances.

Want structured learning?

Take the full Consul course →