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.