diff --git a/htmx/index.htm b/htmx/index.htm
index 77a484c..ed55bbc 100644
--- a/htmx/index.htm
+++ b/htmx/index.htm
@@ -29,25 +29,39 @@
    <body
       hx-ext="pronom-api-params"
       hx-vals='{"baseURLLocal": "http://0.0.0.0:24001", "baseURL": "https://itn.0.orcfax.io"}'>
-      <h1>ITN diagnostics</h1>
-      <div>
-      <h2>License holders</h2>
-      <div>
-      <div>
-      <div
-         hx-get="{baseURL}/api/participants"
-         hx-trigger="load"
-         hx-swap="innerHTML"
-         />
-      </div>
+      <section id="license-holders">
+         <h1>ITN diagnostics</h1>
+         <div>
+         <h2>License holders</h2>
+         <div>
+         <div>
+         <div
+            hx-get="{baseURL}/api/participants"
+            hx-trigger="load"
+            hx-swap="innerHTML"
+            />
+         </div>
+      </section>
       <hr>
-      <h2>Active collector counts</h2>
-      <div
-         hx-get="{baseURL}/api/online_collectors"
-         hx-trigger="load"
-         hx-swap="innerHTML"
-         />
-      </div>
+      <section id="collector-counts">
+         <h2>Active collector counts</h2>
+         <div
+            hx-get="{baseURL}/api/online_collectors"
+            hx-trigger="load"
+            hx-swap="innerHTML"
+            />
+         </div>
+      </section>
+      <hr>
+      <section id="locations">
+         <h2>Collector locations</h2>
+         <div
+            hx-get="{baseURL}/locations"
+            hx-trigger="load"
+            hx-swap="innerHTML"
+            />
+         </div>
+      </section>
       <hr>
       <div class="footer__bottom text--center">
          <div class="footer__copyright">ITN | Diagnostics</div>
diff --git a/src/itn_api/api.py b/src/itn_api/api.py
index 9e80fd6..047709a 100644
--- a/src/itn_api/api.py
+++ b/src/itn_api/api.py
@@ -201,6 +201,17 @@ async def get_itn_aliases_and_staking_csv(
     return reports.get_all_license_holders_csv(app, min_stake, sort)
 
 
+@app.get("/geo", tags=[TAG_STATISTICS])
+async def get_locations():
+    """Return countries participating in the ITN."""
+    return await reports.get_locations(app)
+
+
+# HTMX #################################################################
+# HTMX #################################################################
+# HTMX #################################################################
+
+
 @app.get("/participants", tags=[TAG_HTMX], response_class=HTMLResponse)
 async def get_itn_participants() -> str:
     """Return ITN aliases and licenses."""
@@ -223,6 +234,13 @@ async def get_online_collectors() -> str:
     return htmx.strip()
 
 
+@app.get("/locations", tags=[TAG_HTMX], response_class=HTMLResponse)
+async def get_locations_hx():
+    """Return countries participating in the ITN."""
+    locations = await reports.get_locations(app)
+    return htm_helpers.locations_table(locations)
+
+
 def main():
     """Primary entry point for this script."""
 
diff --git a/src/itn_api/htm_helpers.py b/src/itn_api/htm_helpers.py
index f2122e2..feec128 100644
--- a/src/itn_api/htm_helpers.py
+++ b/src/itn_api/htm_helpers.py
@@ -81,3 +81,38 @@ def participants_count_table(participants_count_total):
         rows = f"{rows}{row}\n"
 
     return f"{head}\n{rows}</table>\n"
+
+
+def locations_table(locations):
+    """Create a table for participant locations."""
+
+    logging.info("formatting participants table")
+
+    if not locations:
+        return "no locations available"
+
+    head = """
+<table>
+    <tr>
+        <th>Region</th>
+        <th>Country</th>
+    </tr>
+    """.strip()
+
+    seen = []
+    rows = ""
+    for locale in locations:
+        region = locale["region"]
+        country = locale["country"]
+        if (region, country) in seen:
+            continue
+        row = f"""
+<tr>
+    <td>{region}</td>
+    <td nowrap>&nbsp;{country}&nbsp;</td>
+</tr>
+        """.strip()
+        seen.append((region, country))
+        rows = f"{rows}{row}\n"
+
+    return f"{head}\n{rows}</table>\n"
diff --git a/src/itn_api/reports.py b/src/itn_api/reports.py
index 8a679ce..8997df9 100644
--- a/src/itn_api/reports.py
+++ b/src/itn_api/reports.py
@@ -2,11 +2,13 @@
 
 # pylint: disable=R0917,R0913,R0914
 
+import json
 import logging
 from collections import Counter
 from dataclasses import dataclass
 from typing import List, Tuple
 
+import apsw
 import humanize
 from fastapi import FastAPI
 
@@ -303,3 +305,32 @@ async def get_date_ranges(app: FastAPI):
         "earliest_date": dates[0],
         "latest_date": dates[1],
     }
+
+
+async def get_locations(app: FastAPI) -> list:
+    """Return locations from the database."""
+    try:
+        unique_raw_data = app.state.connection.execute(
+            "select min(node_id), raw_data from data_points group by node_id;"
+        )
+    except apsw.SQLError:
+        return "zero collectors online"
+    res = list(unique_raw_data)
+    countries = []
+    for item in res:
+        node = item[0]
+        message = json.loads(item[1])
+        try:
+            loc = message["message"]["identity"]["location"]
+            countries.append(
+                (
+                    {
+                        "geo": loc.get("loc"),
+                        "region": loc.get("region"),
+                        "country": loc.get("country"),
+                    }
+                )
+            )
+        except KeyError as err:
+            logger.error("node: '%s' not reporting location (%s)", node, err)
+    return countries