Shipping Costs

Shipping Costs Guide

This guide explains how to calculate shipping costs for outbound orders using the Spreetail Channel Integration API.

Overview

The shipping cost calculation endpoint allows you to:

  • Get accurate shipping cost estimates
  • Compare different shipping options
  • Calculate costs before order creation
  • Optimize shipping decisions
ℹ️

Note: /outbound-cost remains available on API Hub for backward compatibility.

Endpoint

POST /outbound-cost

Full URL: https://api.spreetaileu.com/api/api/v1/outbound-cost

Authentication

This endpoint accepts either:

  • Bearer token: Authorization: Bearer <access_token>
  • ApiKey: Authorization: ApiKey CLIENT_ID:CLIENT_SECRET

Request Structure

To calculate shipping costs, provide:

  • Ship To: Destination country code using shipTo
  • Ship From: Optional origin country code via shipFrom (item-level)
  • Items: Array of items, each with:
    • shipTo (string): Destination country code (ISO 3166-1 alpha-2)
    • shipFrom (string, optional): Origin country code
    • sku (string): Product SKU identifier
    • weight (number): Item weight in kilograms (minimum: 0.01, maximum: 10000)
    • length (number): Item length in centimeters (minimum: 0.01, maximum: 10000)
    • width (number): Item width in centimeters (minimum: 0.01, maximum: 10000)
    • height (number): Item height in centimeters (minimum: 0.01, maximum: 10000)

Required Fields

FieldTypeDescription
items[].shipTostringDestination country code (ISO 3166-1 alpha-2, exactly 2 characters). Required per item.
items[].shipFromstringOptional origin country code. If omitted: UK destinations default to UK, non-UK destinations default to DE.
itemsarrayArray of items to ship (minimum 1 item required)
items[].skustringProduct SKU identifier
items[].weightnumberItem weight in kilograms (0.01 - 10000)
items[].lengthnumberItem length in centimeters (0.01 - 10000)
items[].widthnumberItem width in centimeters (0.01 - 10000)
items[].heightnumberItem height in centimeters (0.01 - 10000)

Example Request: Single Item

{
  "items": [
    {
      "shipTo": "DE",
      "shipFrom": "DE",
      "sku": "PROD-12345",
      "weight": 2.5,
      "length": 20,
      "width": 15,
      "height": 10
    }
  ]
}

Example Request: Multiple Items with Different SKUs

When calculating shipping for an order with multiple different products, include each item with its unique SKU:

{
  "items": [
    {
      "sku": "PROD-12345",
      "shipTo": "GB",
      "shipFrom": "DE",
      "weight": 2.5,
      "length": 20,
      "width": 15,
      "height": 10
    },
    {
      "sku": "PROD-67890",
      "shipTo": "FR",
      "weight": 1.2,
      "length": 15,
      "width": 10,
      "height": 5
    },
    {
      "sku": "WIDGET-XYZ",
      "shipTo": "FR",
      "weight": 0.8,
      "length": 12,
      "width": 8,
      "height": 6
    }
  ]
}
ℹ️

Multiple SKUs: Each item in the items array is routed independently using item-level shipTo and optional shipFrom.

Code Examples

Python

import requests

def calculate_shipping_cost(access_token, cost_data):
    """Calculate shipping cost for an order"""
    url = "https://api.spreetaileu.com/api/api/v1/outbound-cost"
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    response = requests.post(url, headers=headers, json=cost_data)
    response.raise_for_status()
    return response.json()

# Example usage: Single item
cost_request_single = {
    "items": [
        {
            "shipTo": "DE",
            "shipFrom": "DE",
            "sku": "PROD-12345",
            "weight": 2.5,
            "length": 20,
            "width": 15,
            "height": 10
        }
    ]
}

# Example usage: Multiple items with different SKUs
cost_request_multiple = {
    "items": [
        {
            "sku": "PROD-12345",
            "shipTo": "GB",
            "shipFrom": "DE",
            "weight": 2.5,
            "length": 20,
            "width": 15,
            "height": 10
        },
        {
            "sku": "PROD-67890",
            "shipTo": "FR",
            "weight": 1.2,
            "length": 15,
            "width": 10,
            "height": 5
        },
        {
            "sku": "WIDGET-XYZ",
            "shipTo": "FR",
            "weight": 0.8,
            "length": 12,
            "width": 8,
            "height": 6
        }
    ]
}

result = calculate_shipping_cost("your-access-token", cost_request_multiple)

# Display eligible carriers by SKU
if result['success']:
    for sku_result in result['data']['items']:
        print(f"\nSKU: {sku_result['sku']}")
        print("Available shipping options:")
        for carrier in sku_result['eligible_carriers']:
            print(f"  {carrier['carrier_name']} ({carrier['service_name']}): "
                  f"{carrier['cost']} {carrier['currency']} - "
                  f"{carrier['estimated_delivery_days']} days")
        if sku_result['rejected_carriers']:
            print("Unavailable carriers:")
            for carrier in sku_result['rejected_carriers']:
                print(f"  {carrier['carrier_name']}: {carrier['reason']}")

JavaScript/Node.js

async function calculateShippingCost(accessToken, costData) {
  const url = 'https://api.spreetaileu.com/api/api/v1/outbound-cost';
  
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(costData)
  });
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Shipping cost calculation failed: ${error.error || response.statusText}`);
  }
  
  return await response.json();
}

// Example usage: Single item
const costRequestSingle = {
  items: [
    {
      shipTo: 'DE',
      shipFrom: 'DE',
      sku: 'PROD-12345',
      weight: 2.5,
      length: 20,
      width: 15,
      height: 10
    }
  ]
};

// Example usage: Multiple items with different SKUs
const costRequestMultiple = {
  items: [
    {
      sku: 'PROD-12345',
      shipTo: 'GB',
      shipFrom: 'DE',
      weight: 2.5,
      length: 20,
      width: 15,
      height: 10
    },
    {
      sku: 'PROD-67890',
      shipTo: 'FR',
      weight: 1.2,
      length: 15,
      width: 10,
      height: 5
    },
    {
      sku: 'WIDGET-XYZ',
      shipTo: 'FR',
      weight: 0.8,
      length: 12,
      width: 8,
      height: 6
    }
  ]
};

calculateShippingCost('your-access-token', costRequestMultiple)
  .then(result => {
    if (result.success) {
      result.data.items.forEach((skuResult) => {
        console.log(`\nSKU: ${skuResult.sku}`);
        console.log('Available shipping options:');
        skuResult.eligible_carriers.forEach(carrier => {
          console.log(
            `${carrier.carrier_name} (${carrier.service_name}): ` +
            `${carrier.cost} ${carrier.currency} - ` +
            `${carrier.estimated_delivery_days} days`
          );
        });
        if (skuResult.rejected_carriers.length > 0) {
          console.log('Unavailable carriers:');
          skuResult.rejected_carriers.forEach(carrier => {
            console.log(`${carrier.carrier_name}: ${carrier.reason}`);
          });
        }
      });
    }
  })
  .catch(error => console.error('Error:', error));

Response Format

Success Response (200)

{
  "success": true,
  "data": {
    "items": [
      {
        "sku": "PROD-12345",
        "eligible_carriers": [
          {
            "carrier_name": "DHL",
            "service_name": "Express",
            "cost": 12.5,
            "currency": "EUR",
            "estimated_delivery_days": 2,
            "tracking_available": true
          }
        ],
        "rejected_carriers": [
          {
            "carrier_name": "Royal Mail",
            "service_name": "Standard",
            "reason": "Destination not supported"
          }
        ]
      }
    ]
  }
}

Response Fields

FieldTypeDescription
data.itemsarrayResults grouped by input SKU
data.items[].skustringInput SKU
data.items[].eligible_carriersarrayCarriers that can fulfill that SKU shipment
data.items[].eligible_carriers[].carrier_namestringName of the shipping carrier
data.items[].eligible_carriers[].service_namestringService level name
data.items[].eligible_carriers[].costnumberShipping cost
data.items[].eligible_carriers[].currencystringCurrency code (ISO 4217)
data.items[].rejected_carriersarrayCarriers rejected for that SKU
data.items[].rejected_carriers[].carrier_namestringName of the shipping carrier
data.items[].rejected_carriers[].reasonstringReason for rejection

Use Cases

1. Compare Shipping Options

def compare_shipping_options(access_token, destination, items):
    """Compare different shipping options for a shipment"""
    cost_request = {
        "shipTo": destination,
        "items": items
    }
    
    result = calculate_shipping_cost(access_token, cost_request)
    
    if result['success']:
        eligible = result['data']['items'][0]['eligible_carriers'] if result['data']['items'] else []
        
        # Sort by cost
        sorted_by_cost = sorted(eligible, key=lambda x: x['cost'])
        
        # Sort by delivery time
        sorted_by_time = sorted(eligible, key=lambda x: x['estimated_delivery_days'])
        
        return {
            "cheapest": sorted_by_cost[0] if sorted_by_cost else None,
            "fastest": sorted_by_time[0] if sorted_by_time else None,
            "all_options": eligible
        }
    
    return None

# Usage: Multiple items with different SKUs
items = [
    {
        "sku": "PROD-12345",
        "weight": 2.5,
        "length": 20,
        "width": 15,
        "height": 10
    },
    {
        "sku": "PROD-67890",
        "weight": 1.2,
        "length": 15,
        "width": 10,
        "height": 5
    },
    {
        "sku": "WIDGET-XYZ",
        "weight": 0.8,
        "length": 12,
        "width": 8,
        "height": 6
    }
]

options = compare_shipping_options("your-access-token", "DE", items)
if options:
    print(f"Cheapest: {options['cheapest']['carrier_name']} - {options['cheapest']['cost']} EUR")
    print(f"Fastest: {options['fastest']['carrier_name']} - {options['fastest']['estimated_delivery_days']} days")

2. Validate Shipping Feasibility

def check_shipping_feasibility(access_token, destination, items):
    """Check if shipping is possible to a destination"""
    cost_request = {
        "shipTo": destination,
        "items": items
    }
    
    result = calculate_shipping_cost(access_token, cost_request)
    
    if result['success']:
        first_item = result['data']['items'][0] if result['data']['items'] else {'eligible_carriers': [], 'rejected_carriers': []}
        eligible_count = len(first_item['eligible_carriers'])
        rejected_count = len(first_item['rejected_carriers'])
        
        return {
            "feasible": eligible_count > 0,
            "eligible_carriers": eligible_count,
            "rejected_carriers": rejected_count,
            "options": first_item['eligible_carriers']
        }
    
    return {"feasible": False, "error": "Calculation failed"}

# Usage
feasibility = check_shipping_feasibility("your-access-token", "DE", items)
if feasibility['feasible']:
    print(f"Shipping available via {feasibility['eligible_carriers']} carrier(s)")
else:
    print("Shipping not available to this destination")

Important Notes

Endpoint Enablement

  • No endpoint-specific enablement is required for /outbound-cost

Authentication

  • Use either:
  • Authorization: Bearer <access_token>
  • Authorization: ApiKey CLIENT_ID:CLIENT_SECRET

Country Code

  • shipTo must be a valid ISO 3166-1 alpha-2 country code
  • Exactly 2 characters (e.g., "DE", "GB", "US")
  • Case-sensitive (use uppercase)

Ship From Defaults

  • Pass shipFrom per item when you need a specific origin
  • If omitted, fallback is destination-aware:
    • UK destination -> UK
    • non-UK destination -> DE

Weight and Dimensions

  • Weight: Must be in kilograms (minimum: 0.01, maximum: 10000)
  • Dimensions: Must be in centimeters (minimum: 0.01, maximum: 10000)
  • All dimensions are required for each item
  • Providing accurate measurements improves cost calculation accuracy

Items Array

  • At least one item is required
  • Each item must include all required fields: sku, weight, length, width, height
  • Multiple items in one request represent a combined shipment

Error Handling

Endpoint Not Enabled (403)

If the endpoint is not enabled for your account:

{
  "success": false,
  "error": "Unauthorized"
}

Solution: Contact [email protected] to enable the feature.

Invalid Request (400)

{
  "success": false,
  "error": "Invalid request (missing fields, invalid values)"
}

Solution: Verify all required fields are present and values are within valid ranges.

Authentication Error (401)

{
  "success": false,
  "error": "Unauthorized"
}

Solution: Provide a valid Bearer token or ApiKey credentials.

Best Practices

  1. Calculate Before Order Creation: Get shipping costs before finalizing orders
  2. Cache Results: Cache shipping costs for similar destinations/items
  3. Handle Errors: Implement fallback for when endpoint is unavailable
  4. Compare Options: Use the response to compare multiple carrier options
  5. Consider Delivery Time: Factor in estimated delivery days when selecting carriers
  6. Validate Dimensions: Ensure weight and dimensions are accurate for better cost estimates

Related Endpoints

  • POST /outbound - Create order (use shipping cost information in order)
  • GET /order/{referenceNumber} - Check order status and actual shipping details