FedEx API获取运费报价返回404错误排查求助
集成FedEx API获取运费报价时持续收到404错误求助
我正在Node.js应用中集成FedEx API以获取运费报价,已遵循官方文档操作,但持续收到404错误,错误信息为:
"The resource you requested is no longer available."
错误响应详情:
FedEx API request failed with status: 404 - Response body: {"transactionId": "c2dda9ea-61fa-411a-9e39-e70239ee9370","errors":[{"code":"NOT.FOUND.ERROR","message":"The resource you requested is no longer available. Please modify your request and try again."}]}
我已对照FedEx API文档验证了端点URL和请求负载,以下是index.js中的相关代码:
const express = require('express'); const bodyParser = require('body-parser'); const fetch = require('node-fetch'); const path = require('path'); const app = express(); app.use(bodyParser.json()); const FEDEX_OAUTH_URL = 'https://apis.fedex.com/oauth/token'; const FEDEX_API_URL = 'https://apis.fedex.com/rate/v1/rates/quote'; const FEDEX_API_KEY = 'your_api_key'; const FEDEX_SECRET = 'your_api_secret'; const FEDEX_ACCOUNT_NUMBER = 'your_account_number'; async function getFedexToken() { const response = await fetch(FEDEX_OAUTH_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: FEDEX_API_KEY, client_secret: FEDEX_SECRET }) }); const responseBody = await response.text(); if (!response.ok) { throw new Error(`Failed to get FedEx OAuth token: ${response.statusText}. Response Body: ${responseBody}`); } const data = JSON.parse(responseBody); return data.access_token; } app.post('/compare', async (req, res) => { const { originPostcode, originCountry, destinationPostcode, destinationCountry, weight } = req.body; const fedexRequest = { accountNumber: { value: FEDEX_ACCOUNT_NUMBER }, requestedShipment: { shipper: { address: { postalCode: originPostcode, countryCode: originCountry.toUpperCase() } }, recipient: { address: { postalCode: destinationPostcode, countryCode: destinationCountry.toUpperCase() } }, shipDateStamp: new Date().toISOString().split('T')[0], // Today's date in YYYY-MM-DD format pickupType: 'DROPOFF_AT_FEDEX_LOCATION', serviceType: 'INTERNATIONAL_PRIORITY', rateRequestType: ['LIST', 'ACCOUNT'], customsClearanceDetail: { dutiesPayment: { paymentType: 'SENDER', payor: { responsibleParty: null } }, commodities: [ { description: 'Camera', quantity: 1, quantityUnits: 'PCS', weight: { units: 'KG', value: weight }, customsValue: { amount: 100, currency: 'USD' } } ] }, requestedPackageLineItems: [ { weight: { units: 'KG', value: weight } } ] } }; console.log('FedEx Request Payload:', JSON.stringify(fedexRequest, null, 2)); try { const token = await getFedexToken(); const response = await fetch(FEDEX_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-locale': 'en_US', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(fedexRequest) }); const responseBody = await response.text(); if (!response.ok) { console.error('FedEx API request failed with status:', response.status); console.error('Response body:', responseBody); // Log the detailed error response throw new Error(`FedEx API request failed with status ${response.status}: ${responseBody}`); } const fedexData = JSON.parse(responseBody); console.log('FedEx Response Data:', JSON.stringify(fedexData, null, 2)); if (!fedexData.output || !fedexData.output.rateReplyDetails) { throw new Error(`Unexpected FedEx API response structure: ${JSON.stringify(fedexData)}`); } const results = fedexData.output.rateReplyDetails.map(detail => ({ courier: 'FedEx', cost: detail.ratedShipmentDetails[0].totalNetChargeWithDutiesAndTaxes.amount, service: detail.serviceType, transitTime: detail.commitDetails[0].commitTimestamp })); res.json({ results }); } catch (error) { console.error('Error fetching data from FedEx API:', error); res.status(500).json({ error: error.message }); } }); app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); const PORT = process.env.PORT || 3001; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }).on('error', (err) => { if (err.code === 'EADDRINUSE') { console.error(`Port ${PORT} is already in use.`); } else { console.error(err); } });
我预期能获取对应货运详情的FedEx报价,但始终收到404错误,已验证端点和请求结构,仍不清楚原因。现求助:
- 获取报价的端点URL是否正确?
- 请求负载结构是否符合FedEx API要求?
- 是否存在我遗漏的必要请求头或参数?
问题排查与解决方案
1. 端点URL验证
你当前使用的https://apis.fedex.com/rate/v1/rates/quote是旧版Rate API的废弃端点,FedEx最新Rate API v1的正确端点为https://apis.fedex.com/rate/v1/rates/quotes(末尾为复数quotes)。404错误的核心原因就是端点路径错误,旧资源已被移除。
如果是测试环境,需替换为沙箱端点:https://apis-sandbox.fedex.com/rate/v1/rates/quotes,同时OAuth令牌也要从沙箱环境获取(https://apis-sandbox.fedex.com/oauth/token),禁止混用生产与沙箱环境的配置。
2. 请求负载结构修正
你的请求负载存在3处不符合FedEx API要求的问题:
customsClearanceDetail.payor.responsibleParty不能为null:即使由发货方支付关税,也需填写该字段的基本信息,至少包含账号或地址,示例:"payor": { "responsibleParty": { "accountNumber": { "value": FEDEX_ACCOUNT_NUMBER } } }shipper缺少contact信息:FedEx API强制要求发货方地址关联联系人信息,至少包含姓名或电话,示例:"shipper": { "address": { "postalCode": originPostcode, "countryCode": originCountry.toUpperCase() }, "contact": { "personName": "Shipper Name", "phoneNumber": "1234567890" } }requestedPackageLineItems缺少包装类型:需添加packagingType字段,可使用官方指定代码如"packagingType": "YOUR_PACKAGING"。
3. 必要请求头与参数检查
- 补充
X-Customer-Transaction-Id请求头:FedEx API要求该字段用于请求追踪,可生成一个UUID作为值,示例:headers: { 'Content-Type': 'application/json', 'X-locale': 'en_US', 'Authorization': `Bearer ${token}`, 'X-Customer-Transaction-Id': '随机UUID字符串' } - 验证API密钥权限:登录FedEx开发者控制台,确认你的API密钥已开通Rate API的访问权限,部分测试密钥可能仅开放特定API权限。
- 确认账号有效性:确保
FEDEX_ACCOUNT_NUMBER是已激活的有效账号,且与API密钥关联。
内容的提问来源于stack exchange,提问作者Bigshow77
相关产品推荐
相关产品推荐

