UPS Rate API Response Mapping
This reference guide describes how the UPS Rating API response is transformed into the internal ShippingRate type using the Adapter pattern.
Official Documentation
- UPS Rating API - Main API reference
- UPS API Documentation (GitHub) - OpenAPI specification
- UPS Rating on Postman - Interactive API testing
- UPS Developer Portal - Developer home
Response Overview
The UPS Rating API returns rated shipments in RateResponse.RatedShipment[]. Each entry represents a different service option with its associated charges and delivery estimates.
Field Mapping
| ShippingRate Field | UPS Response Path | Notes |
|---|---|---|
id | Generated | ups-${Service.Code}-${timestamp} |
carrier | Constant | 'UPS' |
serviceCode | Service.Code | e.g., "03", "02", "01", "14" |
serviceName | Service.Description | e.g., "UPS Ground", "UPS 2nd Day Air" |
speed | Derived from Service.Code | See speed mapping below |
features | Derived from response | See features extraction below |
baseRate | BaseServiceCharge.MonetaryValue | Parse to number |
additionalFees | ItemizedCharges[] | Mapped to Fee[] |
totalCost | NegotiatedRateCharges.TotalCharge.MonetaryValue (preferred) or TotalCharges.MonetaryValue | Parse to number |
estimatedDeliveryDate | GuaranteedDelivery.ScheduledDeliveryDate or TimeInTransit.ServiceSummary.EstimatedArrival.Arrival.Date | Parse to Date |
guaranteedDelivery | TimeInTransit.ServiceSummary.GuaranteedIndicator | Check if present/truthy |
Speed Mapping
Map Service.Code to internal ServiceSpeed:
typescript1const speedMap: Record<string, ServiceSpeed> = {2 // Next Day3 '14': 'overnight', // UPS Next Day Air Early4 '01': 'overnight', // UPS Next Day Air5 '13': 'overnight', // UPS Next Day Air Saver67 // Two Day8 '02': 'two-day', // UPS 2nd Day Air9 '59': 'two-day', // UPS 2nd Day Air A.M.1011 // Standard12 '12': 'standard', // UPS 3 Day Select13 '65': 'standard', // UPS Worldwide Saver14 '08': 'standard', // UPS Worldwide Expedited1516 // Economy/Ground17 '03': 'economy', // UPS Ground18 '11': 'economy', // UPS Standard (Canada)19 '07': 'economy', // UPS Worldwide Express20 '54': 'economy', // UPS Worldwide Express Plus21};
Features Extraction
Extract features from multiple response fields:
typescript1function extractFeatures(shipment: RatedShipment): string[] {2 const features: string[] = [];3 const transit = shipment.TimeInTransit?.ServiceSummary;45 // Guaranteed delivery6 if (transit?.GuaranteedIndicator) {7 features.push('Guaranteed Delivery');8 }910 // Saturday delivery11 if (transit?.SaturdayDelivery) {12 features.push('Saturday Delivery Available');13 }1415 // Sunday delivery16 if (transit?.SundayDelivery) {17 features.push('Sunday Delivery Available');18 }1920 // Transit days21 const days = transit?.EstimatedArrival?.BusinessDaysInTransit;22 if (days) {23 features.push(`${days} Business Day${days !== '1' ? 's' : ''}`);24 }2526 // Delivery time27 if (shipment.GuaranteedDelivery?.DeliveryByTime) {28 features.push(`By ${shipment.GuaranteedDelivery.DeliveryByTime}`);29 }3031 return features;32}
ItemizedCharges to Fee Mapping
Map ItemizedCharges[] to internal Fee[]:
typescript1// Common UPS charge codes2const chargeCodeMap: Record<string, FeeType | null> = {3 '100': null, // Base Service Charge (not a fee)4 '375': 'fuel', // Fuel Surcharge5 '376': 'signature', // Delivery Area Surcharge6 '190': 'signature', // Residential Delivery7 '374': 'fragile', // Large Package Surcharge8 '400': 'insurance', // Declared Value9 '377': 'saturdayDelivery', // Saturday Delivery10};1112function mapItemizedChargeToFee(charge: UPSItemizedCharge): Fee {13 return {14 type: chargeCodeMap[charge.Code] ?? 'insurance',15 amount: parseFloat(charge.MonetaryValue),16 description: charge.Description,17 };18}
Rate Selection
UPS provides both list rates and negotiated rates. Prefer negotiated rates when available:
typescript1function extractTotalCost(shipment: RatedShipment): number {2 // Prefer negotiated rates (account-specific discounts)3 if (shipment.NegotiatedRateCharges?.TotalCharge?.MonetaryValue) {4 return parseFloat(shipment.NegotiatedRateCharges.TotalCharge.MonetaryValue);5 }67 // Fall back to standard total charges8 return parseFloat(shipment.TotalCharges.MonetaryValue);9}1011function extractBaseRate(shipment: RatedShipment): number {12 // Prefer negotiated base charge13 const negotiatedBase = shipment.NegotiatedRateCharges?.BaseServiceCharge;14 if (negotiatedBase?.[0]?.MonetaryValue) {15 return parseFloat(negotiatedBase[0].MonetaryValue);16 }1718 // Fall back to standard base charge19 return parseFloat(shipment.BaseServiceCharge.MonetaryValue);20}
Complete Adapter Implementation
typescript1function adaptUPSRate(shipment: RatedShipment): ShippingRate {2 return {3 id: `ups-${shipment.Service.Code}-${Date.now()}`,4 carrier: 'UPS',5 serviceCode: shipment.Service.Code,6 serviceName: shipment.Service.Description,7 speed: speedMap[shipment.Service.Code] ?? 'standard',8 features: extractFeatures(shipment),9 baseRate: extractBaseRate(shipment),10 additionalFees: extractFees(shipment),11 totalCost: extractTotalCost(shipment),12 estimatedDeliveryDate: parseDeliveryDate(shipment),13 guaranteedDelivery: !!shipment.TimeInTransit?.ServiceSummary?.GuaranteedIndicator,14 };15}1617function adaptUPSResponse(response: UPSRateResponse): ShippingRate[] {18 return response.RateResponse.RatedShipment.map(adaptUPSRate);19}
Fee Extraction
Combine fees from multiple sources:
typescript1function extractFees(shipment: RatedShipment): Fee[] {2 const fees: Fee[] = [];34 // Shipment-level itemized charges5 shipment.ItemizedCharges?.forEach(charge => {6 if (charge.Code !== '100') { // Skip base service charge7 fees.push(mapItemizedChargeToFee(charge));8 }9 });1011 // Service options charges (if broken down)12 if (shipment.ServiceOptionsCharges?.MonetaryValue) {13 const amount = parseFloat(shipment.ServiceOptionsCharges.MonetaryValue);14 if (amount > 0) {15 fees.push({16 type: 'insurance',17 amount,18 description: 'Service Options',19 });20 }21 }2223 // Tax charges24 shipment.TaxCharges?.forEach(tax => {25 fees.push({26 type: 'insurance',27 amount: parseFloat(tax.MonetaryValue),28 description: tax.Type,29 });30 });3132 return fees;33}
Delivery Date Parsing
Extract delivery date from multiple possible locations:
typescript1function parseDeliveryDate(shipment: RatedShipment): Date {2 // Primary: Guaranteed delivery date3 if (shipment.GuaranteedDelivery?.ScheduledDeliveryDate) {4 return parseUPSDate(shipment.GuaranteedDelivery.ScheduledDeliveryDate);5 }67 // Secondary: Time in transit arrival8 const arrival = shipment.TimeInTransit?.ServiceSummary?.EstimatedArrival;9 if (arrival?.Arrival?.Date) {10 return parseUPSDate(arrival.Arrival.Date);11 }1213 // Fallback: Calculate from business days14 if (arrival?.BusinessDaysInTransit) {15 return calculateBusinessDaysFromNow(parseInt(arrival.BusinessDaysInTransit));16 }1718 // Last resort: Scheduled delivery date at shipment level19 if (shipment.ScheduledDeliveryDate) {20 return parseUPSDate(shipment.ScheduledDeliveryDate);21 }2223 throw new Error('Unable to determine delivery date');24}2526// UPS dates are typically YYYYMMDD format27function parseUPSDate(dateStr: string): Date {28 const year = dateStr.substring(0, 4);29 const month = dateStr.substring(4, 6);30 const day = dateStr.substring(6, 8);31 return new Date(`${year}-${month}-${day}`);32}
Error Handling
Handle response status and alerts:
typescript1function validateResponse(response: UPSRateResponse): void {2 const status = response.RateResponse.Response.ResponseStatus;34 if (status.Code !== '1') {5 throw new CarrierError('UPS', status.Description, false);6 }7}89function handleAlerts(response: UPSRateResponse): void {10 const alerts = response.RateResponse.Response.Alert ?? [];1112 alerts.forEach(alert => {13 // Log for monitoring14 console.warn(`UPS Alert [${alert.Code}]: ${alert.Description}`);15 });1617 // Check for critical alerts in AlertDetail18 const details = response.RateResponse.Response.AlertDetail ?? [];19 const criticalErrors = details.filter(d => d.Level === 'E');2021 if (criticalErrors.length > 0) {22 throw new CarrierError('UPS', criticalErrors[0].Description, false);23 }24}
Example Transformation
Input (one rated shipment from UPS response):
json1{2 "Service": {3 "Code": "03",4 "Description": "UPS Ground"5 },6 "BillingWeight": {7 "UnitOfMeasurement": { "Code": "LBS" },8 "Weight": "22"9 },10 "BaseServiceCharge": {11 "CurrencyCode": "USD",12 "MonetaryValue": "45.50"13 },14 "ItemizedCharges": [15 {16 "Code": "375",17 "Description": "Fuel Surcharge",18 "CurrencyCode": "USD",19 "MonetaryValue": "4.55"20 }21 ],22 "TotalCharges": {23 "CurrencyCode": "USD",24 "MonetaryValue": "50.05"25 },26 "NegotiatedRateCharges": {27 "TotalCharge": {28 "CurrencyCode": "USD",29 "MonetaryValue": "42.50"30 }31 },32 "TimeInTransit": {33 "ServiceSummary": {34 "GuaranteedIndicator": "",35 "EstimatedArrival": {36 "Arrival": { "Date": "20240120" },37 "BusinessDaysInTransit": "5"38 }39 }40 },41 "GuaranteedDelivery": {42 "BusinessDaysInTransit": "5",43 "ScheduledDeliveryDate": "20240120"44 }45}
Output (ShippingRate):
typescript1{2 id: 'ups-03-1697654321000',3 carrier: 'UPS',4 serviceCode: '03',5 serviceName: 'UPS Ground',6 speed: 'economy',7 features: ['5 Business Days'],8 baseRate: 45.50,9 additionalFees: [{10 type: 'fuel',11 amount: 4.55,12 description: 'Fuel Surcharge'13 }],14 totalCost: 42.50, // Uses negotiated rate15 estimatedDeliveryDate: new Date('2024-01-20'),16 guaranteedDelivery: false17}
Currency Handling
UPS returns currency codes with monetary values. Ensure consistent handling:
typescript1interface MonetaryValue {2 CurrencyCode: string;3 MonetaryValue: string;4}56function parseMonetary(value: MonetaryValue): { amount: number; currency: string } {7 return {8 amount: parseFloat(value.MonetaryValue),9 currency: value.CurrencyCode,10 };11}1213// Note: Currency conversion should be handled at the RateService level14// if comparing rates across carriers with different currencies