diff --git a/compendium-frontend/src/App.tsx b/compendium-frontend/src/App.tsx
index 07848769fb712f69958d341106eaf766a8a4641d..5b383036d9d1281ae7686fce13dd6c545de0e185 100644
--- a/compendium-frontend/src/App.tsx
+++ b/compendium-frontend/src/App.tsx
@@ -13,6 +13,7 @@ import ParentOrganisation from "./pages/ParentOrganisation";
 import ECProjects from "./pages/ECProjects";
 import Providers from "./Providers";
 import PolicyPage from "./pages/Policy";
+import TrafficVolumePage from "./pages/TrafficVolumePerNren";
 import ConnectedInstitutionsURLs from "./pages/ConnectedInstitutionsURLs";
 
 
@@ -26,6 +27,7 @@ const router = createBrowserRouter([
   { path: "/parentorganisation", element: <ParentOrganisation />},
   { path: "/ec-projects", element: <ECProjects />},
   { path: "/policy", element: <PolicyPage />},
+  { path: "/traffic-volume", element: <TrafficVolumePage />},
   { path: "/data", element: <CompendiumData />},
   { path: "/institutions-urls", element: <ConnectedInstitutionsURLs />},
   { path: "*", element: <Landing />},
diff --git a/compendium-frontend/src/Schema.tsx b/compendium-frontend/src/Schema.tsx
index 569d2b3acca8d15a933b8480babb1db50c9f3096..77de018feb51e053f3b219480fe646f3849ac5ff 100644
--- a/compendium-frontend/src/Schema.tsx
+++ b/compendium-frontend/src/Schema.tsx
@@ -18,6 +18,13 @@ export interface Budget extends NrenAndYearDatapoint {
     budget: string
 }
 
+export interface TrafficVolume extends NrenAndYearDatapoint {
+    from_customers: number,
+    to_customers: number,
+    from_external: number,
+    to_external: number
+}
+
 export interface FundingSource extends NrenAndYearDatapoint {
     client_institutions: number,
     commercial: number,
diff --git a/compendium-frontend/src/components/DataPage.tsx b/compendium-frontend/src/components/DataPage.tsx
index dc1e28a97d8af5cc4ba9d6ef995a8440d448c1ed..32c3a3b6c41b0caf4b3ac87060dee02f4790db1e 100644
--- a/compendium-frontend/src/components/DataPage.tsx
+++ b/compendium-frontend/src/components/DataPage.tsx
@@ -9,6 +9,7 @@ import PolicySidebar from "./PolicySidebar";
 
 import { Chart as ChartJS } from 'chart.js';
 import { usePreview } from "../helpers/usePreview";
+import NetworkSidebar from "./NetworkSidebar";
 import ConnectedUsersSidebar from "./ConnectedUsersSidebar";
 
 ChartJS.defaults.font.size = 16;
@@ -31,6 +32,7 @@ function DataPage({ title, description, filter, children, category }: inputProps
         <>
             {category === Sections.Organisation && <OrganizationSidebar />}
             {category === Sections.Policy && <PolicySidebar />}
+            {category === Sections.Network && <NetworkSidebar />}
             {category === Sections.ConnectedUsers && <ConnectedUsersSidebar />}
             <PageHeader type={'data'} />
             { preview && <Row className="preview-banner">
diff --git a/compendium-frontend/src/components/NetworkSidebar.tsx b/compendium-frontend/src/components/NetworkSidebar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1274242afcc736e29c95897df673f32ff8dee36d
--- /dev/null
+++ b/compendium-frontend/src/components/NetworkSidebar.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { Row } from 'react-bootstrap';
+import Sidebar from './SideBar';
+
+const PolicySidebar = () => {
+    return (
+        <Sidebar>
+            <h5>Network</h5>
+            <Row>
+                <Link to="/traffic-volume" className="link-text-underline">
+                    <span>Traffic volume</span>
+                </Link>
+            </Row>
+        </Sidebar>
+    )
+}
+
+export default PolicySidebar
\ No newline at end of file
diff --git a/compendium-frontend/src/components/SectionNavigation.tsx b/compendium-frontend/src/components/SectionNavigation.tsx
index 4e1acb158a5b8becc6dbaec42e577281a8aa5ee6..f43534b2a5881d47b54a72f5c5355c9da825efdf 100644
--- a/compendium-frontend/src/components/SectionNavigation.tsx
+++ b/compendium-frontend/src/components/SectionNavigation.tsx
@@ -32,10 +32,9 @@ const SectionNavigation = ({ activeCategory }: inputProps) => {
                         <span>{Sections.ConnectedUsers}</span>
                     </Button>
                     <Button
-                        onClick={() => navigate(activeCategory === Sections.Network ? '.' : '.')}
+                        onClick={() => navigate(activeCategory === Sections.Network ? '.' : '/traffic-volume')}
                         variant={'nav-box'}
-                        active={activeCategory === Sections.Network}
-                        disabled={true}>
+                        active={activeCategory === Sections.Network}>
                         <span>{Sections.Network}</span>
                     </Button>
                     <Button
diff --git a/compendium-frontend/src/helpers/dataconversion.tsx b/compendium-frontend/src/helpers/dataconversion.tsx
index 7810c479f16c6d45eb381a4041295620d9ecae59..9123fe00026a20c1d9fc14670e8ce6761eea16c1 100644
--- a/compendium-frontend/src/helpers/dataconversion.tsx
+++ b/compendium-frontend/src/helpers/dataconversion.tsx
@@ -1,7 +1,7 @@
 import { cartesianProduct } from 'cartesian-product-multiple-arrays';
 import {
-    FundingSource, FundingSourceDataset, ChargingStructure,
-    Budget, BasicDataset, NrenStaff, NrenStaffDataset, Organisation, ECProject, Policy, ConnectedInstitutionURLs
+    FundingSource, FundingSourceDataset, ChargingStructure, ConnectedInstitutionURLs,
+    Budget, BasicDataset, NrenStaff, NrenStaffDataset, Organisation, ECProject, Policy, TrafficVolume
 } from "../Schema";
 
 // create a color from a string, credits https://stackoverflow.com/a/16348977
@@ -54,6 +54,50 @@ function CreateDataLookup(data: FundingSource[]) {
     return dataLookup
 }
 
+export const createTrafficVolumeDataset = (fundingSourcesData: TrafficVolume[]) => {
+    const data = fundingSourcesData;
+    const dataLookup = new Map<string, number>();
+    data.forEach((item: TrafficVolume) => {
+        const lookupKey = `${item.nren}/${item.year}`;
+        console.log(lookupKey );
+        dataLookup.set(lookupKey, item.from_customers);  // we ignore the rest of the data for now..
+    })
+
+    const labelsYear = [...new Set(data.map((item: TrafficVolume) => item.year))];
+    const labelsNREN = [...new Set(data.map((item: TrafficVolume) => item.nren))];
+
+    const sets = labelsYear.map(year => {
+        return {
+            backgroundColor: 'rgba(40, 40, 250, 0.8)',
+            data: labelsNREN.map((nren) => dataLookup.get(`${nren}/${year}`) ?? null),
+            label: year.toString(),
+            borderSkipped: true,
+            barPercentage: 0.8,
+            borderWidth: 0.5,
+            categoryPercentage: 0.8,
+            hidden: false,
+            datalabels: {
+                display: true,
+                color: 'grey',
+                formatter: function(value, context) {
+                    return context.dataset.label;
+                },
+                anchor: 'start',
+                align: 'end',
+                offset: function(context) {
+                    return context.chart.chartArea.width;
+                }
+            }
+        }
+    });
+
+    const dataResponse: BasicDataset = {
+        datasets: sets,
+        labels: labelsNREN.map(l => l.toString())
+    }
+    return dataResponse;
+}
+
 export const createFundingSourceDataset = (fundingSourcesData: FundingSource[]) => {
     const data = fundingSourcesData;
     const dataLookup = CreateDataLookup(data)
diff --git a/compendium-frontend/src/pages/CompendiumData.tsx b/compendium-frontend/src/pages/CompendiumData.tsx
index b1c625acb72fe87450120d56c4fc489bfec28e8c..00f383892c0bc95f855dc2ed3d74a22743f4be44 100644
--- a/compendium-frontend/src/pages/CompendiumData.tsx
+++ b/compendium-frontend/src/pages/CompendiumData.tsx
@@ -98,7 +98,11 @@ function CompendiumData(): ReactElement {
                         </CollapsibleBox>
                         <CollapsibleBox title={Sections.Network} startCollapsed>
                             <div className="collapsible-column">
-                                <h5>Coming Soon</h5>
+                                <Row>
+                                    <Link to="/traffic-volume" className="link-text-underline">
+                                        <span>Total yearly traffic volume per NREN</span>
+                                    </Link>
+                                </Row>
                             </div>
                         </CollapsibleBox>
                         <CollapsibleBox title={Sections.Services} startCollapsed>
diff --git a/compendium-frontend/src/pages/TrafficVolumePerNren.tsx b/compendium-frontend/src/pages/TrafficVolumePerNren.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5cd86d80df332dfe0175c3160cf583ff43ef58a2
--- /dev/null
+++ b/compendium-frontend/src/pages/TrafficVolumePerNren.tsx
@@ -0,0 +1,127 @@
+import React, { useContext } from 'react';
+import { Bar } from 'react-chartjs-2';
+import { Col, Row } from "react-bootstrap";
+import { Chart as ChartJS } from 'chart.js';
+import ChartDataLabels from 'chartjs-plugin-datalabels';
+
+import { TrafficVolume } from "../Schema";
+import { createTrafficVolumeDataset } from "../helpers/dataconversion";
+import DataPage from '../components/DataPage';
+import Filter from "../components/graphing/Filter"
+import { ExportType, Sections } from '../helpers/constants';
+import DownloadDataButton from "../components/DownloadDataButton";
+import { FilterSelectionContext } from '../helpers/FilterSelectionProvider';
+import DownloadImageChartButton from "../components/DownloadImageChartButton";
+import ChartContainer from "../components/graphing/ChartContainer";
+import { useData } from '../helpers/useData';
+
+export const chartOptions = {
+  maintainAspectRatio: false,
+  layout: {
+    padding: {
+      right: 60
+    }
+  },
+  animation: {
+    duration: 0,
+  },
+  plugins: {
+    legend: {
+      display: false
+    }
+  },
+  scales: {
+    x: {
+      position: "top" as const
+    },
+    xBottom: {
+      grid: {
+        drawOnChartArea: false
+      },
+      afterDataLimits: function (axis) {
+        const indices = Object.keys(ChartJS.instances)
+
+        // initial values should be far outside possible range
+        let max = -99999999
+        let min = 99999999
+
+        for (const index of indices) {
+          if (ChartJS.instances[index] && axis.chart.scales.xBottom) {
+            min = Math.min(ChartJS.instances[index].scales.x.min, min);
+            max = Math.max(ChartJS.instances[index].scales.x.max, max);
+          }
+        }
+
+        axis.chart.scales.xBottom.options.min = min;
+        axis.chart.scales.xBottom.options.max = max;
+        axis.chart.scales.xBottom.min = min;
+        axis.chart.scales.xBottom.max = max;
+      },
+    },
+    y: {
+      ticks: {
+        autoSkip: false
+      },
+    }
+  },
+  indexAxis: "y" as const,
+};
+
+function TrafficVolumePage() {
+  const { filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
+  const { data: trafficVolumeData, years, nrens } = useData<TrafficVolume>('/api/traffic/', setFilterSelection);
+
+  const trafficVolumeDataset = createTrafficVolumeDataset(trafficVolumeData);
+
+  trafficVolumeDataset.datasets.forEach(dataset => {
+    dataset.hidden = !filterSelection.selectedYears.includes(parseInt(dataset.label));
+  });
+
+  // remove the datapoints and labels for the nrens that aren't selected
+  // unfortunately we cannot just hide them because chart.js doesn't want
+  // to create a stack from a single dataset
+  trafficVolumeDataset.datasets.forEach(dataset => {
+    dataset.data = dataset.data.filter((e, i) => {
+      return filterSelection.selectedNrens.includes(trafficVolumeDataset.labels[i]);
+    });
+  });
+  trafficVolumeDataset.labels = trafficVolumeDataset.labels.filter((e) => filterSelection.selectedNrens.includes(e));
+
+  const filterNode = <Filter
+    filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
+    filterSelection={filterSelection}
+    setFilterSelection={setFilterSelection}
+  />
+
+  const numNrens = filterSelection.selectedNrens.length;
+  const numYears = filterSelection.selectedYears.length;
+  const heightPerBar = 2; // every added bar should give this much additional height
+
+  console.log(trafficVolumeDataset)
+
+  // set a minimum height of 20rem, additional years need some more space
+  const height = numNrens * numYears * heightPerBar + 5;
+  return (
+    <DataPage title="Traffic Volume Of NRENs per Year"
+      description='Total yearly traffic volume in terabyte per NREN'
+      category={Sections.Network} filter={filterNode}>
+      <>
+        <Row>
+          <DownloadDataButton data={trafficVolumeData} filename="traffic_volume_of_nren_per_year.csv" exportType={ExportType.CSV} />
+          <DownloadDataButton data={trafficVolumeData} filename="traffic_volume_of_nren_per_year.xlsx" exportType={ExportType.EXCEL} />
+        </Row>
+        <DownloadImageChartButton filename="traffic_volume_of_nren_per_year" />
+        <ChartContainer>
+          <div className="chart-container" style={{ 'height': `${height}rem` }}>
+            <Bar
+              plugins={[ChartDataLabels]}
+              data={trafficVolumeDataset}
+              options={chartOptions}
+            />
+          </div>
+        </ChartContainer>
+      </>
+    </DataPage>
+  );
+}
+export default TrafficVolumePage;
diff --git a/compendium_v2/background_task/parse_excel_data.py b/compendium_v2/background_task/parse_excel_data.py
index 162ec80d78eadb50fdc08664ba5ade43a2c13968..aba3ef6fb09ce2ec5f7b523788dfe22c2a7ff00c 100644
--- a/compendium_v2/background_task/parse_excel_data.py
+++ b/compendium_v2/background_task/parse_excel_data.py
@@ -10,6 +10,7 @@ setup_logging()
 logger = logging.getLogger(__name__)
 
 EXCEL_FILE = os.path.join(os.path.dirname(__file__), "xlsx", "2021_Organisation_DataSeries.xlsx")
+NETWORK_EXCEL_FILE = os.path.join(os.path.dirname(__file__), "xlsx", "2022_Networks_DataSeries.xlsx")
 
 
 def fetch_budget_excel_data():
@@ -347,3 +348,47 @@ def fetch_organization_excel_data():
 
         if parent_org not in [None, 'NA', 'N/A']:
             yield nren.upper(), 2021, parent_org
+
+
+def fetch_traffic_excel_data():
+    # load the xlsx file
+    wb = openpyxl.load_workbook(NETWORK_EXCEL_FILE, data_only=True, read_only=True)
+
+    # select the active worksheet
+    sheet_name = "Estimated_Traffic TByte"
+    ws = wb[sheet_name]
+
+    rows = list(ws.rows)
+
+    def convert_number(value, nren, year, description):
+        if value is None or value == '--' or value == 'No data':
+            return 0
+        try:
+            return float(value)
+        except (TypeError, ValueError):
+            logger.info(f'NREN: {nren} year: {year} has {value} for {description}; set to 0.')
+            return 0
+
+    def create_points_for_year(year, start_column):
+        for i in range(6, 49):
+            nren_name = rows[i][start_column].value
+            if not nren_name:
+                continue
+            nren_name = nren_name.upper()
+
+            from_external = convert_number(rows[i][start_column + 1].value, nren_name, year, 'from_external')
+            to_external = convert_number(rows[i][start_column + 2].value, nren_name, year, 'to_external')
+            from_customer = convert_number(rows[i][start_column + 3].value, nren_name, year, 'from_customer')
+            to_customer = convert_number(rows[i][start_column + 4].value, nren_name, year, 'to_customer')
+            if from_external == 0 and to_external == 0 and from_customer == 0 and to_customer == 0:
+                continue
+
+            yield nren_name, year, from_external, to_external, from_customer, to_customer
+
+    yield from create_points_for_year(2016, 38)
+    yield from create_points_for_year(2017, 32)
+    yield from create_points_for_year(2018, 26)
+    yield from create_points_for_year(2019, 20)
+    yield from create_points_for_year(2020, 14)
+    yield from create_points_for_year(2021, 8)
+    yield from create_points_for_year(2022, 2)
diff --git a/compendium_v2/db/model.py b/compendium_v2/db/model.py
index e822beba649484cb9ea358f8e5780c89ada52148..d619c1f362e0855015838400fc319fc4c102ffb6 100644
--- a/compendium_v2/db/model.py
+++ b/compendium_v2/db/model.py
@@ -4,7 +4,7 @@ from __future__ import annotations
 import logging
 from decimal import Decimal
 from enum import Enum
-from typing import Optional
+from typing import List, Optional
 from typing_extensions import Annotated
 
 from sqlalchemy import String, JSON
@@ -126,10 +126,21 @@ class Policy(db.Model):
     data_protection: Mapped[str]
 
 
+class TrafficVolume(db.Model):
+    __tablename__ = 'traffic_volume'
+    nren_id: Mapped[int_pk_fkNREN]
+    nren: Mapped[NREN] = relationship(lazy='joined')
+    year: Mapped[int_pk]
+    to_customers: Mapped[Decimal]
+    from_customers: Mapped[Decimal]
+    to_external: Mapped[Decimal]
+    from_external: Mapped[Decimal]
+
+
 class InstitutionURLs(db.Model):
     __tablename__ = 'institution_urls'
 
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
     year: Mapped[int_pk]
-    urls: Mapped[list[str]] = mapped_column(JSON)
+    urls: Mapped[List[str]] = mapped_column(JSON)
diff --git a/compendium_v2/migrations/versions/3730c7f1ea1b_add_traffic_volume_table.py b/compendium_v2/migrations/versions/3730c7f1ea1b_add_traffic_volume_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..60a2aee854916f118bc7d9c1bb00d0814c02373a
--- /dev/null
+++ b/compendium_v2/migrations/versions/3730c7f1ea1b_add_traffic_volume_table.py
@@ -0,0 +1,38 @@
+"""add traffic volume table
+
+Revision ID: 3730c7f1ea1b
+Revises: d6f581374e8f
+Create Date: 2023-09-01 10:26:29.089050
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '3730c7f1ea1b'
+down_revision = 'f2879a6b15c8'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.create_table(
+        'traffic_volume',
+        sa.Column('nren_id', sa.Integer(), nullable=False),
+        sa.Column('year', sa.Integer(), nullable=False),
+        sa.Column('to_customers', sa.Numeric(), nullable=False),
+        sa.Column('from_customers', sa.Numeric(), nullable=False),
+        sa.Column('to_external', sa.Numeric(), nullable=False),
+        sa.Column('from_external', sa.Numeric(), nullable=False),
+        sa.ForeignKeyConstraint(['nren_id'], ['nren.id'], name=op.f('fk_traffic_volume_nren_id_nren')),
+        sa.PrimaryKeyConstraint('nren_id', 'year', name=op.f('pk_traffic_volume'))
+    )
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_table('traffic_volume')
+    # ### end Alembic commands ###
diff --git a/compendium_v2/publishers/survey_publisher_v1.py b/compendium_v2/publishers/survey_publisher_v1.py
index 0731fe5b1a2752b060d4242aee6c54f16b9c3a65..a6e2376f5f1f12571e6f8fefa8b89c04fb405a3e 100644
--- a/compendium_v2/publishers/survey_publisher_v1.py
+++ b/compendium_v2/publishers/survey_publisher_v1.py
@@ -204,6 +204,27 @@ def db_organizations_migration(nren_dict):
     db.session.commit()
 
 
+def db_traffic_volume_migration(nren_dict):
+    traffic_data = parse_excel_data.fetch_traffic_excel_data()
+    for (abbrev, year, from_external, to_external, from_customers, to_customers) in traffic_data:
+        if abbrev not in nren_dict:
+            logger.warning(f'{abbrev} unknown. Skipping.')
+            continue
+
+        nren = nren_dict[abbrev]
+        traffic_entry = model.TrafficVolume(
+            nren=nren,
+            nren_id=nren.id,
+            year=year,
+            from_customers=from_customers,
+            to_customers=to_customers,
+            from_external=from_external,
+            to_external=to_external
+        )
+        db.session.merge(traffic_entry)
+    db.session.commit()
+
+
 def _cli(config, app):
     with app.app_context():
         nren_dict = helpers.get_uppercase_nren_dict()
@@ -213,6 +234,7 @@ def _cli(config, app):
         db_staffing_migration(nren_dict)
         db_ecprojects_migration(nren_dict)
         db_organizations_migration(nren_dict)
+        db_traffic_volume_migration(nren_dict)
 
 
 @click.command()
diff --git a/compendium_v2/routes/api.py b/compendium_v2/routes/api.py
index cbd15590bc9216fcbb6b40404e6678bf803102f5..9af16ad2ced857457ba14daa49cf8ab841a9c81b 100644
--- a/compendium_v2/routes/api.py
+++ b/compendium_v2/routes/api.py
@@ -14,6 +14,7 @@ from compendium_v2.routes.survey import routes as survey
 from compendium_v2.routes.user import routes as user_routes
 from compendium_v2.routes.nren import routes as nren_routes
 from compendium_v2.routes.response import routes as response_routes
+from compendium_v2.routes.traffic import routes as traffic_routes
 from compendium_v2.routes.institutions_urls import routes as institutions_urls_routes
 
 routes = Blueprint('compendium-v2-api', __name__)
@@ -28,6 +29,7 @@ routes.register_blueprint(survey, url_prefix='/survey')
 routes.register_blueprint(user_routes, url_prefix='/user')
 routes.register_blueprint(nren_routes, url_prefix='/nren')
 routes.register_blueprint(response_routes, url_prefix='/response')
+routes.register_blueprint(traffic_routes, url_prefix='/traffic')
 routes.register_blueprint(institutions_urls_routes, url_prefix='/institutions-urls')
 
 logger = logging.getLogger(__name__)
diff --git a/compendium_v2/routes/traffic.py b/compendium_v2/routes/traffic.py
new file mode 100644
index 0000000000000000000000000000000000000000..70255d0f847be12d54b62308cdc4763e6b263dde
--- /dev/null
+++ b/compendium_v2/routes/traffic.py
@@ -0,0 +1,68 @@
+import logging
+
+from flask import Blueprint, jsonify
+
+from compendium_v2.routes import common
+from compendium_v2.db.model import TrafficVolume
+from typing import Any
+
+
+routes = Blueprint('traffic', __name__)
+logger = logging.getLogger(__name__)
+
+TRAFFIC_RESPONSE_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+
+    'definitions': {
+        'traffic': {
+            'type': 'object',
+            'properties': {
+                'nren': {'type': 'string'},
+                'nren_country': {'type': 'string'},
+                'year': {'type': 'integer'},
+                'to_customers': {'type': 'number'},
+                'from_customers': {'type': 'number'},
+                'to_external': {'type': 'number'},
+                'from_external': {'type': 'number'}
+            },
+            'required': ['nren', 'nren_country', 'year', 'to_customers',
+                         'from_customers', 'to_external', 'from_external'],
+            'additionalProperties': False
+        }
+    },
+
+    'type': 'array',
+    'items': {'$ref': '#/definitions/traffic'}
+}
+
+
+@routes.route('/', methods=['GET'])
+@common.require_accepts_json
+def traffic_volume_view() -> Any:
+    """
+    handler for /api/traffic/ requests
+
+    response will be formatted as:
+
+    .. asjson::
+        compendium_v2.routes.traffic.TRAFFIC_RESPONSE_SCHEMA
+
+    :return:
+    """
+
+    def _extract_data(entry: TrafficVolume):
+        return {
+            'nren': entry.nren.name,
+            'nren_country': entry.nren.country,
+            'year': entry.year,
+            'to_customers': float(entry.to_customers),
+            'from_customers': float(entry.from_customers),
+            'to_external': float(entry.to_external),
+            'from_external': float(entry.from_external)
+        }
+
+    entries = sorted(
+        [_extract_data(entry) for entry in common.get_data(TrafficVolume)],
+        key=lambda d: (d['nren'], d['year'])
+    )
+    return jsonify(entries)
diff --git a/test/conftest.py b/test/conftest.py
index 9dda0738deb84bee45fb5d48ce2c100e35751ebb..638a3f21f18cc1dfe4494fc5769037196aebc66f 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -333,6 +333,30 @@ def test_policy_data(app):
         db.session.commit()
 
 
+@pytest.fixture
+def test_traffic_data(app):
+    with app.app_context():
+        nrens_and_years = [('nren1', 2019), ('nren1', 2020), ('nren1', 2021), ('nren2', 2019), ('nren2', 2021)]
+        nren_names = set(ny[0] for ny in nrens_and_years)
+        nren_dict = {nren_name: model.NREN(name=nren_name, country='country') for nren_name in nren_names}
+        db.session.add_all(nren_dict.values())
+
+        for (nren_name, year) in nrens_and_years:
+            nren = nren_dict[nren_name]
+
+            db.session.add(
+                model.TrafficVolume(
+                    nren=nren,
+                    year=year,
+                    from_customers=2.23,
+                    to_customers=5.2,
+                    from_external=0,
+                    to_external=1000
+                )
+            )
+        db.session.commit()
+
+
 @pytest.fixture
 def test_institution_urls_data(app):
     def _create_and_save_nrens(nren_names):
diff --git a/test/test_traffic.py b/test/test_traffic.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1427f5b5ec23aae6f5f2a38e3c918e5d79732d5
--- /dev/null
+++ b/test/test_traffic.py
@@ -0,0 +1,13 @@
+import json
+import jsonschema
+from compendium_v2.routes.traffic import TRAFFIC_RESPONSE_SCHEMA
+
+
+def test_staff_response(client, test_traffic_data):
+    rv = client.get(
+        '/api/traffic/',
+        headers={'Accept': ['application/json']})
+    assert rv.status_code == 200
+    result = json.loads(rv.data.decode('utf-8'))
+    jsonschema.validate(result, TRAFFIC_RESPONSE_SCHEMA)
+    assert result