30 minlesson

UPS Rate API Response Mapping

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


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 FieldUPS Response PathNotes
idGeneratedups-${Service.Code}-${timestamp}
carrierConstant'UPS'
serviceCodeService.Codee.g., "03", "02", "01", "14"
serviceNameService.Descriptione.g., "UPS Ground", "UPS 2nd Day Air"
speedDerived from Service.CodeSee speed mapping below
featuresDerived from responseSee features extraction below
baseRateBaseServiceCharge.MonetaryValueParse to number
additionalFeesItemizedCharges[]Mapped to Fee[]
totalCostNegotiatedRateCharges.TotalCharge.MonetaryValue (preferred) or TotalCharges.MonetaryValueParse to number
estimatedDeliveryDateGuaranteedDelivery.ScheduledDeliveryDate or TimeInTransit.ServiceSummary.EstimatedArrival.Arrival.DateParse to Date
guaranteedDeliveryTimeInTransit.ServiceSummary.GuaranteedIndicatorCheck if present/truthy

Speed Mapping

Map Service.Code to internal ServiceSpeed:

typescript
1const speedMap: Record<string, ServiceSpeed> = {
2 // Next Day
3 '14': 'overnight', // UPS Next Day Air Early
4 '01': 'overnight', // UPS Next Day Air
5 '13': 'overnight', // UPS Next Day Air Saver
6
7 // Two Day
8 '02': 'two-day', // UPS 2nd Day Air
9 '59': 'two-day', // UPS 2nd Day Air A.M.
10
11 // Standard
12 '12': 'standard', // UPS 3 Day Select
13 '65': 'standard', // UPS Worldwide Saver
14 '08': 'standard', // UPS Worldwide Expedited
15
16 // Economy/Ground
17 '03': 'economy', // UPS Ground
18 '11': 'economy', // UPS Standard (Canada)
19 '07': 'economy', // UPS Worldwide Express
20 '54': 'economy', // UPS Worldwide Express Plus
21};

Features Extraction

Extract features from multiple response fields:

typescript
1function extractFeatures(shipment: RatedShipment): string[] {
2 const features: string[] = [];
3 const transit = shipment.TimeInTransit?.ServiceSummary;
4
5 // Guaranteed delivery
6 if (transit?.GuaranteedIndicator) {
7 features.push('Guaranteed Delivery');
8 }
9
10 // Saturday delivery
11 if (transit?.SaturdayDelivery) {
12 features.push('Saturday Delivery Available');
13 }
14
15 // Sunday delivery
16 if (transit?.SundayDelivery) {
17 features.push('Sunday Delivery Available');
18 }
19
20 // Transit days
21 const days = transit?.EstimatedArrival?.BusinessDaysInTransit;
22 if (days) {
23 features.push(`${days} Business Day${days !== '1' ? 's' : ''}`);
24 }
25
26 // Delivery time
27 if (shipment.GuaranteedDelivery?.DeliveryByTime) {
28 features.push(`By ${shipment.GuaranteedDelivery.DeliveryByTime}`);
29 }
30
31 return features;
32}

ItemizedCharges to Fee Mapping

Map ItemizedCharges[] to internal Fee[]:

typescript
1// Common UPS charge codes
2const chargeCodeMap: Record<string, FeeType | null> = {
3 '100': null, // Base Service Charge (not a fee)
4 '375': 'fuel', // Fuel Surcharge
5 '376': 'signature', // Delivery Area Surcharge
6 '190': 'signature', // Residential Delivery
7 '374': 'fragile', // Large Package Surcharge
8 '400': 'insurance', // Declared Value
9 '377': 'saturdayDelivery', // Saturday Delivery
10};
11
12function 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:

typescript
1function 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 }
6
7 // Fall back to standard total charges
8 return parseFloat(shipment.TotalCharges.MonetaryValue);
9}
10
11function extractBaseRate(shipment: RatedShipment): number {
12 // Prefer negotiated base charge
13 const negotiatedBase = shipment.NegotiatedRateCharges?.BaseServiceCharge;
14 if (negotiatedBase?.[0]?.MonetaryValue) {
15 return parseFloat(negotiatedBase[0].MonetaryValue);
16 }
17
18 // Fall back to standard base charge
19 return parseFloat(shipment.BaseServiceCharge.MonetaryValue);
20}

Complete Adapter Implementation

typescript
1function 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}
16
17function adaptUPSResponse(response: UPSRateResponse): ShippingRate[] {
18 return response.RateResponse.RatedShipment.map(adaptUPSRate);
19}

Fee Extraction

Combine fees from multiple sources:

typescript
1function extractFees(shipment: RatedShipment): Fee[] {
2 const fees: Fee[] = [];
3
4 // Shipment-level itemized charges
5 shipment.ItemizedCharges?.forEach(charge => {
6 if (charge.Code !== '100') { // Skip base service charge
7 fees.push(mapItemizedChargeToFee(charge));
8 }
9 });
10
11 // 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 }
22
23 // Tax charges
24 shipment.TaxCharges?.forEach(tax => {
25 fees.push({
26 type: 'insurance',
27 amount: parseFloat(tax.MonetaryValue),
28 description: tax.Type,
29 });
30 });
31
32 return fees;
33}

Delivery Date Parsing

Extract delivery date from multiple possible locations:

typescript
1function parseDeliveryDate(shipment: RatedShipment): Date {
2 // Primary: Guaranteed delivery date
3 if (shipment.GuaranteedDelivery?.ScheduledDeliveryDate) {
4 return parseUPSDate(shipment.GuaranteedDelivery.ScheduledDeliveryDate);
5 }
6
7 // Secondary: Time in transit arrival
8 const arrival = shipment.TimeInTransit?.ServiceSummary?.EstimatedArrival;
9 if (arrival?.Arrival?.Date) {
10 return parseUPSDate(arrival.Arrival.Date);
11 }
12
13 // Fallback: Calculate from business days
14 if (arrival?.BusinessDaysInTransit) {
15 return calculateBusinessDaysFromNow(parseInt(arrival.BusinessDaysInTransit));
16 }
17
18 // Last resort: Scheduled delivery date at shipment level
19 if (shipment.ScheduledDeliveryDate) {
20 return parseUPSDate(shipment.ScheduledDeliveryDate);
21 }
22
23 throw new Error('Unable to determine delivery date');
24}
25
26// UPS dates are typically YYYYMMDD format
27function 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:

typescript
1function validateResponse(response: UPSRateResponse): void {
2 const status = response.RateResponse.Response.ResponseStatus;
3
4 if (status.Code !== '1') {
5 throw new CarrierError('UPS', status.Description, false);
6 }
7}
8
9function handleAlerts(response: UPSRateResponse): void {
10 const alerts = response.RateResponse.Response.Alert ?? [];
11
12 alerts.forEach(alert => {
13 // Log for monitoring
14 console.warn(`UPS Alert [${alert.Code}]: ${alert.Description}`);
15 });
16
17 // Check for critical alerts in AlertDetail
18 const details = response.RateResponse.Response.AlertDetail ?? [];
19 const criticalErrors = details.filter(d => d.Level === 'E');
20
21 if (criticalErrors.length > 0) {
22 throw new CarrierError('UPS', criticalErrors[0].Description, false);
23 }
24}

Example Transformation

Input (one rated shipment from UPS response):

json
1{
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):

typescript
1{
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 rate
15 estimatedDeliveryDate: new Date('2024-01-20'),
16 guaranteedDelivery: false
17}

Currency Handling

UPS returns currency codes with monetary values. Ensure consistent handling:

typescript
1interface MonetaryValue {
2 CurrencyCode: string;
3 MonetaryValue: string;
4}
5
6function parseMonetary(value: MonetaryValue): { amount: number; currency: string } {
7 return {
8 amount: parseFloat(value.MonetaryValue),
9 currency: value.CurrencyCode,
10 };
11}
12
13// Note: Currency conversion should be handled at the RateService level
14// if comparing rates across carriers with different currencies