🔄 Modal switch

Simulate the impact on CO₂e emissions when switching between different modes of transport for your shipments, and gain insights on lower-emitting modes.

🎯 What this guide is about

This guide shows you:

  • How to compare CO₂e emissions across different transport modes for the same route.
  • How to handle both simple and more complex modal switches with transshipment points.
  • Example payloads and code to help you integrate into your own app or scripts.

🧠 Scenarios covered

In this guide we’ll look at:

  • Road vs rail vs barge for a single leg;
  • Direct air vs ocean + road + rail with transshipment points;
  • Finding the closest port or rail station if none is provided.

🛠 Steps overview and prerequisites

  1. Build a payload for your shipment (orders + transportChainElements)
  2. Prepare mode-specific details (e.g. fuelType, truckSize)
  3. POST to the /shipment/v2/report/co2 endpoint
  4. Parse the co2e.total value from the JSON response
  5. Repeat for each mode or transshipment combination

Prerequisites

  • ✅ Valid API key for Shipment API v2.1 (ISO 14083-compliant).
  • ⚠️ Note that multi-leg requests return emissions for hubs. Run one request per scenario for easier comparisons.

🧑‍💻Scenarios

1️⃣ Modal switch with direct route

Simulate the CO₂e impact of switching between different modes.

⚙️ Example payload

Start by defining the order and the basic information of the leg :

one_leg_shipment_body = {
    "orders": [
        {
            "type": "fcl",
            "nContainers": 1,
            "containerSizeTypeCode": "40GP"
        }
    ],
    "transportChainElements": [
        {
            "from": "DEDUI",
            "to": "NLRTM",
            "type": "leg",
        }
    ]
}

📝 Mode-specific details

Define the details you'd like to apply for each mode. For example :

road_details = { truckSize: 33, fuelType: "DIESEL" };
rail_details = {};
inland_water_details = { vesselType: "CONTAINER_COUPLED" };

⚙️ Example request

Then, run the calculation to retrieve the emissions.

API_KEY = 'your-api-key'
SHIPMENT_API_URL = 'https://api.searoutes.com/shipment/v2/report/co2'

def one_tce_modal_switch(body, modes_and_details):
    results = []
    modal_body = dict(body)
    headers = {'x-api-key': API_KEY, 'Content-Type': 'application/json', 'Accept-Version': '2.1'}
    for mode_detail in modes_and_details:
        # Update the transport chain elements detail for the current mode
        modal_body["transportChainElements"][0]["mode"] = mode_detail[0]
        modal_body["transportChainElements"][0]["details"] = mode_detail[1]
        # Get the response
        response = requests.post(SHIPMENT_API_URL, headers=headers, json=body)
        data = response.json()
        # Save the total CO2
        results.append(data["co2e"]["total"])
    return results

# Define the modes and their details to be tested
modes_to_test = [
    ("road", road_details),
    ("rail", rail_details),
    ("inland_water", inland_water_details)
]
# Run the calculation
print(one_tce_modal_switch(one_leg_shipment_body, modes_to_test))

✅ Example results

ModeCO₂e WTW (g)
Road585 570
Rail95 076
Barge115 331

2️⃣ Modal switch with known transshipment points

Compare a direct air shipment against a rail-ocean-road alternative.

When changing from one mode to another (for example, air to ocean), you often introduce one or more new transshipment points.

For example, switching from direct air freight to ocean freight usually requires:

  • Rail or road to and from an ocean port
  • Ocean shipping to a destination port

Here, your payload will list each leg explicitly in the transportChainElements.

Here’s a payload to compare a direct air shipment (ATL to LHR) against a combined rail → sea → road itinerary (ATLUSSAVGBSOUGBLHR):

⚙️ Example payload

Start by defining the order and the basic information of the leg :

order = {"type": "PARCEL", "weight": 1000}

📝 Mode-specific details

as well as the combinations of legs you want to compare:

road_details = { "truckSize": 33, "fuelType": "DIESEL" }
rail_details = {}
air_details = { "aircraft": { "type": "CARGO" } }
sea_details = { "carrier": { "scac": "CMDU" } }

alternative1_legs = [
    { "type": "leg", "from": "ATL", "to": "LHR", "mode": "air", "details": air_details }
]

alternative2_legs = [
    { "type": "leg", "from": "USATL", "to": "USSAV", "mode": "rail", "details": rail_details },
    { "type": "leg", "from": "USSAV", "to": "GBSOU", "mode": "sea", "details": sea_details },
    { "type": "leg", "from": "GBSOU", "to": "GBLHR", "mode": "road", "details": road_details }
]

⚙️ Example request

Then, run the calculation to retrieve the emissions.

def compare_different_legs(orders, alternative_legs):
    results = []
    body = {
        "orders": orders,
    }
    headers = {'x-api-key': API_KEY, 'Content-Type': 'application/json', 'Accept-Version': '2.1'}
    for alternative in alternative_legs:
        # Update the transport chain elements
        body["transportChainElements"] = alternative
        # Get the response
        response = requests.post(SHIPMENT_API_URL, headers=headers, json=body)
        data = response.json()
        # Save the total CO2 and the CO2 by leg
        alternative_result = {
            "total": data["co2e"]["total"],
            "by_leg": [tce["co2e"]["total"] for tce in data["transportChainElements"] if tce["type"] == "leg"]
        }
        results.append(alternative_result)
    return results


print(compare_different_legs([order], [alternative1_legs, alternative2_legs]))

✅ Example results

ModeCO₂e WTW (g)Alternative modesCO₂e WTW (g)
TOTAL4 165 206TOTAL70 430
Rail USATL>USSAV7 299
Sea USSAV>GBSOU40 451
Road GBSOU>GBLHR14 183

In this example, switching from direct air to ocean-based routing reduces emissions by 98%.


3️⃣ Modal switch with unknown transshipment points

Simulate a switch to ocean/rail without pre-identified transshipment points — letting Searoutes pick the closest ports/stations for you.

When comparing modes like direct air to ocean-based routes, you may not already know the new transshipment points (ports, etc.). Searoutes’ geocoding API helps you discover the closest ones.

⚙️ Example payload

In this example, we’ll switch a direct air shipment from Chicago (ORD) to Milan (MXP) into an ocean-based shipment, step by step. You will need to use the closest location endpoint & the geocoding/all endpoint.

a. Get airport coordinates
Use the geocoding/all endpoint to retrieve the coordinates of the origin and destination locations given in IATA. Let's write a small function to achieve that :

GEOCODING_ALL_API_URL = "https://api.searoutes.com/geocoding/v2/all"

def get_coordinates_of_iata(iata):
    params = {
        "iataCode": iata,
    }
    headers = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
    response = requests.get(f"{GEOCODING_ALL_API_URL}", headers=headers, params=params)
    data = response.json()
    return data["features"][0]["geometry"]["coordinates"]

Note that you could make this function smarter to handler other types of input such that locode or city name, the geocoding/v2/all handles them all.

b. Find nearest ocean ports

Then, use the geocoding/closest endpoint, searching the closest large/medium ports. Let's write a function to search for the closest location to the given coordinates

GEOCODING_CLOSEST_API_URL = "https://api.searoutes.com/geocoding/v2/closest"

def get_coordinates_of_iata(iata):
    params = {
        "iataCode": iata,
    }
    headers = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
    response = requests.get(f"{GEOCODING_ALL_API_URL}", headers=headers, params=params)
    data = response.json()
    return data["features"][0]["geometry"]["coordinates"]

In this example, you get respectively Baltimore (USBAL) and Genoa (ITGOA) ports.

Note that without passing the large,medium parameter, the port of Marwood Heights, Illinois would have been retrieved as the closest port to Chicago.

c. Build the full payload

Here’s the full python payload after resolving the transshipment points:

def modal_switch_from_air_to_sea_and_road(source, destination, order):
    results = []
    # Retrieve the coordinates of source and destination
    source_coordinates = get_coordinates_of_iata(source)
    destination_coordinates = get_coordinates_of_iata(destination)
    # Get the closest ports
    added_ts_port_1 = get_closest(source_coordinates, "port", "large,medium")
    added_ts_port_2 = get_closest(destination_coordinates, "port", "large,medium")
    # Prepare the body for one air leg
    air_body = {
        "orders": [order],
        "transportChainElements": [
            {
                "type": "leg",
                "mode": "air",
                "from": source,
                "to": destination
            }
        ]
    }
    # Prepare the body for the new legs : road to closest port to source + sea + road from closest port to destination
    alternative_body = {
        "orders": [order],
        "transportChainElements": [
            {
                "type": "leg",
                "from": source,
                "to": added_ts_port_1,
                "mode": "road"
            },
            {
                "type": "leg",
                "from": added_ts_port_1,
                "to": added_ts_port_2,
                "mode": "sea"
            },
            {
                "type": "leg",
                "from": added_ts_port_2,
                "to": destination,
                "mode": "road"
            }
        ]
    }

    # Prepare the header of the requests
    headers = {'x-api-key': API_KEY, 'Content-Type': 'application/json', 'Accept-Version': '2.1'}
    # Pass the request for air
    air_response = requests.post(SHIPMENT_API_URL, headers=headers, json=air_body)
    air_data = air_response.json()
    results.append(
        {
            "total": air_data["co2e"]["total"],
            "by_leg": [tce["co2e"]["total"] for tce in air_data["transportChainElements"] if tce["type"] == "leg"]
        }
    )
    # Pass the request for the alternative legs
    alternative_response = requests.post(SHIPMENT_API_URL, headers=headers, json=alternative_body)
    alternative_data = alternative_response.json()
    results.append(
        {
            "total": alternative_data["co2e"]["total"],
            "by_leg": [tce["co2e"]["total"] for tce in alternative_data["transportChainElements"] if tce["type"] == "leg"]
        }
    )

    return results


# Try on our example
print(modal_switch_from_air_to_sea_and_road("ORD", "MXP", order))

✅ Example results

Direct air (ATL->LHR)CO₂e WTW (g)Alternative modesCO₂e WTW (g)
TOTAL5 791 055TOTAL376 292
Road USORD>USBAL127 911
Sea USBAL>ITGOA74 519
Road ITGOA>ITMXP171 722

In this example, switching from direct air to ocean-based routing reduces emissions by 93%.


✅ What’s next?

  • 🤝 Reach out to our [email protected], we’d love to hear your feedback on these guides or suggestions for new hands‑on topics!
  • 📖 Check the full Shipment API v2.1 docs for all supported params.