by InlinexDev

FedEx API Label Generation: Solving the Most Common Errors

Troubleshooting guide for the most frequent FedEx REST API errors during shipping label generation, with solutions and code examples.

FedEx APIshippingtroubleshootingNode.jse-commerce

FedEx API Is Powerful but Frustrating

FedEx's REST API handles everything from rate quotes to label generation to tracking. But the error messages are often cryptic, documentation has gaps, and the sandbox behaves differently from production. After processing thousands of labels through ShipAnywhere, here are the errors you'll hit and how to fix them.

Error 1: INVALID.INPUT.EXCEPTION (Address Validation)

The most common error. FedEx validates addresses strictly:

{
  "errors": [{
    "code": "INVALID.INPUT.EXCEPTION",
    "message": "Invalid postal code for country."
  }]
}

Solution

Validate addresses before sending to FedEx:

function validateAddress(address) {
  const errors = [];
  
  // Postal code format by country
  const postalPatterns = {
    'US': /^\d{5}(-\d{4})?$/,
    'SG': /^\d{6}$/,
    'GB': /^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$/i,
    'CA': /^[A-Z]\d[A-Z] ?\d[A-Z]\d$/i
  };
  
  const pattern = postalPatterns[address.countryCode];
  if (pattern && !pattern.test(address.postalCode)) {
    errors.push(`Invalid postal code format for ${address.countryCode}`);
  }
  
  if (!address.streetLines?.[0]?.trim()) {
    errors.push('Street address is required');
  }
  
  if (address.streetLines?.[0]?.length > 35) {
    errors.push('Street line 1 must be under 35 characters');
  }
  
  return errors;
}

Key gotcha: FedEx limits street lines to 35 characters each. Long addresses must be split across streetLines[0] and streetLines[1].

Error 2: SERVICE.NOT.AVAILABLE

{
  "errors": [{
    "code": "SERVICE.NOT.AVAILABLE.ERROR",
    "message": "Service is not available for the origin/destination pair."
  }]
}

Solution

Not all FedEx services are available for all routes. Always fetch available services first:

async function getAvailableServices(from, to) {
  const rates = await getRates({
    from,
    to,
    weight: 1, // Minimum weight for service check
    dimensions: { length: 10, width: 10, height: 10 }
  });
  
  return rates.map(r => r.serviceType);
}

// Then validate before creating a shipment
const available = await getAvailableServices(shipper, recipient);
if (!available.includes(selectedService)) {
  throw new Error(`${selectedService} not available for this route`);
}

Error 3: OAUTH Token Expired

{
  "errors": [{
    "code": "NOT.AUTHORIZED.ERROR",
    "message": "Access token has expired or is invalid."
  }]
}

Solution

Implement automatic token refresh with a buffer:

class FedExClient {
  constructor() {
    this.token = null;
    this.tokenExpiry = 0;
  }

  async getToken() {
    // Refresh 5 minutes before expiry
    if (this.token && Date.now() < this.tokenExpiry - 300000) {
      return this.token;
    }

    const response = await fetch(`${FEDEX_BASE}/oauth/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: process.env.FEDEX_CLIENT_ID,
        client_secret: process.env.FEDEX_CLIENT_SECRET
      })
    });

    const data = await response.json();
    this.token = data.access_token;
    this.tokenExpiry = Date.now() + (data.expires_in * 1000);
    return this.token;
  }

  async request(endpoint, body) {
    const token = await this.getToken();
    const response = await fetch(`${FEDEX_BASE}${endpoint}`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(body)
    });

    if (response.status === 401) {
      // Force token refresh and retry
      this.token = null;
      return this.request(endpoint, body);
    }

    return response.json();
  }
}

Error 4: WEIGHT.EXCEEDS.LIMIT

FedEx has weight limits per service type. FedEx Express has different limits than FedEx Ground:

const WEIGHT_LIMITS = {
  'FEDEX_INTERNATIONAL_PRIORITY': 68, // kg
  'FEDEX_INTERNATIONAL_ECONOMY': 68,
  'FEDEX_GROUND': 68,
  'FEDEX_EXPRESS_SAVER': 68,
  'FEDEX_FIRST_OVERNIGHT': 68,
  'FEDEX_FREIGHT_ECONOMY': 9999  // Freight has much higher limits
};

function validateWeight(weight, serviceType) {
  const limit = WEIGHT_LIMITS[serviceType];
  if (weight > limit) {
    return `Weight ${weight}kg exceeds ${limit}kg limit for ${serviceType}`;
  }
  return null;
}

Error 5: CUSTOMS.CLEARANCE Issues

International shipments require customs documentation:

function buildCustomsDetail(items, currency = 'SGD') {
  return {
    dutiesPayment: {
      paymentType: 'SENDER' // or 'RECIPIENT' for DDP
    },
    commodities: items.map(item => ({
      description: item.description.substring(0, 450), // Max 450 chars
      countryOfManufacture: item.originCountry,
      quantity: item.quantity,
      quantityUnits: 'PCS',
      unitPrice: {
        amount: item.unitValue,
        currency
      },
      customsValue: {
        amount: item.unitValue * item.quantity,
        currency
      },
      weight: {
        value: item.weight,
        units: 'KG'
      },
      harmonizedCode: item.hsCode // HS tariff code
    }))
  };
}

Common customs mistakes:

  • Missing HS codes — required for most international shipments
  • Description too vague — "goods" will be rejected; use specific descriptions
  • Declared value too low — customs may hold the package for inspection
  • Missing country of manufacture — required for commercial invoices

Error 6: Sandbox vs Production Differences

The FedEx sandbox is unreliable. Common differences:

| Aspect | Sandbox | Production | |--------|---------|------------| | Rate accuracy | Approximate | Exact | | Label format | Often returns errors | Consistent | | Address validation | Lenient | Strict | | Service availability | Limited | Full | | Tracking numbers | Non-functional | Real |

Recommendation: Test basic flows in sandbox, but do final testing in production with test shipments that you void immediately.

Debugging Tips

Log every FedEx API request and response:

async request(endpoint, body) {
  const requestId = generateId();
  console.log(JSON.stringify({
    type: 'fedex_request',
    requestId,
    endpoint,
    body: sanitizeForLog(body)
  }));

  const response = await fetch(/* ... */);
  const data = await response.json();

  console.log(JSON.stringify({
    type: 'fedex_response',
    requestId,
    status: response.status,
    data
  }));

  return data;
}

This log trail is invaluable when debugging label generation failures in production.

Conclusion

FedEx API integration is a matter of handling edge cases. The happy path works fine — it's the address validation failures, customs documentation requirements, and service availability checks that consume development time. Build comprehensive validation before calling FedEx, and log everything for when things go wrong.

Related Project

ShipAnywhere

Smart international shipping platform offering up to 60% off FedEx rates, with instant quotes, one-click labels, electronic trade documents, and scheduled pickups to 220+ countries.