TNT Express API Integration

This document explains how to integrate TNT Express's ExpressConnect Pricing service into an application to obtain an estimated shipping price for a consignment. It also gives a high-level view of how this could be connected to Odoo, since Odoo has no native TNT connector.

Unlike Lufthansa or Mercedes-Benz, TNT Express does not offer a modern REST/JSON API for this function. ExpressConnect Pricing is an older XML over HTTP interface, still active, that TNT customers use today for rate lookups. Note also that TNT Express was acquired by FedEx in 2016; TNT's own systems (including ExpressConnect) still operate for legacy TNT accounts and shipments, largely in Europe, alongside FedEx's separate API stack.

TNT Express API integration with Odoo

Objectives

  • Understand what ExpressConnect Pricing returns and how it differs from a modern REST pricing API.
  • Register for ExpressConnect access and get valid TNT account credentials.
  • Build a compliant XML price request and submit it correctly.
  • Parse the XML response, including prices, charges, and errors.
  • Establish a practical path to bring TNT price quotes into Odoo.

Key Highlights

  • ExpressConnect Pricing is XML-based, not JSON requests and responses are XML documents posted over HTTP.
  • Authentication is HTTP Basic Auth using a username and password issued by your TNT representative, not OAuth2.
  • A request can batch multiple price checks in one call using repeated priceCheck elements, each tagged with a unique rateId.
  • Pricing needs, at minimum, sender/delivery country (plus town and postcode for accuracy), shipment weight, volume, and number of pieces.
  • Responses return one or more rated services per price check, each with a total price, price excluding VAT, and VAT amount.
  • Optional priceBreakDown returns itemized charge elements such as fuel surcharges and VAT lines.
  • Errors come back as one of three types: runtimeError (system-side), parseError (malformed XML), or brokenRule (bad input data, e.g. invalid postcode).
  • ExpressConnect Pricing does not support non-ASCII characters, and special characters like "&" in addresses must be escaped or wrapped in CDATA.

1. What This API Offers

ExpressConnect Pricing returns an estimated cost for shipping a consignment between two locations, for one or more TNT services. It does not book a shipment or generate a label for that you would use the related ExpressConnect Shipping interface.

A price request can specify:

  • Sender and delivery address (country, and ideally town and postcode)
  • Collection date and time
  • Product/service code (or omit it to get pricing across all valid services, up to 10)
  • Consignment details: total weight, volume, and number of pieces (or itemized piece lines)
  • Account number and account country, for account-specific rates
  • Insurance value and goods value, if insurance is requested
  • Terms of payment (sender pays or receiver pays)
  • Currency for the returned price

If you don't supply town and postcode, TNT falls back to a default depot for the country, which produces a less accurate estimate.

2. Getting Access

Step 1 - Contact your local TNT representative to register for ExpressConnect. There is no self-service signup portal like Lufthansa's or Mercedes-Benz's developer console; access is arranged directly with TNT.

Step 2 - You will be issued a username and password. The same credentials are used across ExpressConnect Pricing, Shipping, and Tracking.

Step 3 - You will also need one or more valid TNT account numbers, each tied to a country, to get account-specific rates rather than standard published rates.

Step 4 - Use the Express Web Services test site (behind the same login) to validate your XML requests before writing code against the live endpoint.

3. Building and Sending a Price Request

Endpoint
POST https://express.tnt.com/expressconnect/pricing/getprice

Authentication: HTTP Basic Auth, using your ExpressConnect username and password in the request header.

Minimal example request

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<priceRequest>
  <appId>PC</appId>
  <appVersion>3.2</appVersion>
  <priceCheck>
    <rateId>rate1</rateId>
    <sender>
      <country>GB</country>
      <town>Atherstone</town>
      <postcode>CV9 2RY</postcode>
    </sender>
    <delivery>
      <country>ES</country>
      <town>Alicante</town>
      <postcode>03006</postcode>
    </delivery>
    <collectionDateTime>2026-09-15T09:00:00</collectionDateTime>
    <product>
      <type>N</type>
    </product>
    <account>
      <accountNumber>123456789</accountNumber>
      <accountCountry>GB</accountCountry>
    </account>
    <currency>GBP</currency>
    <priceBreakDown>true</priceBreakDown>
    <consignmentDetails>
      <totalWeight>1.25</totalWeight>
      <totalVolume>0.1</totalVolume>
      <totalNumberOfPieces>1</totalNumberOfPieces>
    </consignmentDetails>
  </priceCheck>
</priceRequest>

Key fields

  • appId - always "PC".
  • appVersion - 3.2 is current and also returns transit time / estimated delivery data; 3.0 and 2.0 are older, price-only variants.
  • rateId - a key you choose to match each priceCheck to its result in the response. Required if batching multiple checks in one request.
  • product.type - "D" for documents, "N" for non-document packages.
  • product.id - omit to get pricing across all valid services; supply it (e.g. "15N") to price one specific service.
  • You can include several priceCheck blocks in a single priceRequest to batch multiple quotes in one call.

4. Reading the Response

Example response

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<document>
  <requestId>28817</requestId>
  <priceResponse>
    <ratedServices>
      <rateId>rate1</rateId>
      <currency>GBP</currency>
      <ratedService>
        <product>
          <id>09N</id>
          <productDesc>9:00 Express</productDesc>
        </product>
        <totalPrice>288.47</totalPrice>
        <totalPriceExclVat>238.40</totalPriceExclVat>
        <vatAmount>50.07</vatAmount>
      </ratedService>
    </ratedServices>
  </priceResponse>
</document>
  • requestId - quote this if you contact TNT support about a specific request.
  • ratedServices.rateId - matches back to the rateId you sent, so you can associate results with the original request when batching.
  • Each ratedService gives totalPrice, totalPriceExclVat, and vatAmount for one available service.
  • If priceBreakDown was set to true, each ratedService also includes a chargeElements breakdown (e.g. fuel surcharge, VAT line items).
  • With appVersion 3.2, each product also returns estimatedTimeOfArrival and collection cut-off times, useful if you want to show price and transit time together.

Errors are returned inside an errors element as one of three types:

  • runtimeError - system-side failure; advise the user to retry or contact TNT support.
  • parseError - malformed request XML; a development-time issue to fix in your code.
  • brokenRule - bad input data (invalid postcode, unknown account number, missing mandatory field), tagged with a messageType of Information, Warning, or Error, plus a rateId so you know which price check it relates to.

5. Practical Considerations

  • This is an XML interface, not JSON plan to build and parse XML rather than call a REST/JSON SDK.
  • There's no OAuth token to refresh; credentials are sent with every request via Basic Auth, so store them securely as you would any password.
  • Because it's a legacy interface, response times and availability are worth monitoring in production build retry logic around runtimeError.
  • Since TNT is now part of FedEx, if you're integrating a new account or region, check with your TNT/FedEx representative whether ExpressConnect is still the correct interface for that account, or whether it has moved to a FedEx API.
  • Non-ASCII characters aren't supported; sanitize address and reference fields before sending.

6. Overview: Connecting This to Odoo

Odoo has no native TNT connector, so this is a custom module. Because the interface is XML/Basic-Auth rather than OAuth2/JSON, the Odoo-side plumbing is simpler in one respect (no token refresh) but requires XML handling instead of JSON.

Option A - On-Demand Rate Lookup at Checkout / Delivery Creation

  • Add a button on the Sales Order or Delivery form (Odoo's Inventory/Sales modules) that builds a priceCheck XML request from the order's shipping address and package details.
  • Use Python's standard library (e.g. lxml or xml.etree.ElementTree) to build the request and parse the response inside the module.
  • Send the request with Python's requests library, using HTTP Basic Auth with the stored ExpressConnect credentials.
  • Display the returned rate(s) to the user, and optionally store the chosen price on the order.

Option B - Bulk/Batch Quoting

  • If you need quotes for many open orders at once, build the requests as a single priceRequest with multiple priceCheck elements (one per order), each with its own rateId set to the Odoo record ID.
  • Parse the response and match each ratedServices.rateId back to the originating Odoo record to update prices in bulk.

Need a Similar Odoo Integration

Talk to our Odoo team about connecting TNT Express, or any other third-party courier and shipping provider, with your Odoo Website.

WhatsApp