FedEx is a global express carrier and one of the two international couriers most Indian D2C brands use for cross border parcels, alongside DHL. For a commerce database FedEx is a carrier connector, not a sales channel: there are no orders, listings or stock behind the API. What it gives us is the shipment side of the record, rates and transit times before we book, an AWB (FedEx calls it a tracking number) and a label when we book, a pickup confirmation, a full scan event history while the parcel moves, and a signature proof of delivery document at the end. The modern surface is the REST API set on developer.fedex.com, which replaced the older FedEx Web Services SOAP toolkit. Everything below refers to that REST set, verified against the official FedEx OpenAPI specifications.
At a glance
The developer.fedex.com HTML documentation pages would not render for an unauthenticated fetcher on 2026-09-21: catalog.html and several guide pages returned a FedEx error page or a 404. The endpoint paths, field names and sample values on this page therefore come from the official FedEx OpenAPI specification files, which are published by FedEx and mirrored in the ToolJet marketplace plugin. The per API conceptual pages under developer.fedex.com/api/en-us/catalog/ did render and are cited where used.
What it is
FedEx Corporation is a United States listed logistics company. The operating companies that matter to a parcel integration are FedEx Express (international and domestic air express, the network behind most API service types), FedEx Ground and FedEx Home Delivery (United States and Canada surface), FedEx Freight (LTL, priced through a separate Freight LTL API), and FedEx Ground Economy, formerly SmartPost, which hands the last mile to the United States Postal Service.
For an Indian seller FedEx is mainly the international export lane: a D2C brand shipping to the United States, the Gulf or Europe will quote FedEx International Priority or International Economy against Aramex and DHL. FedEx also runs domestic Indian movements, and the official Rate API specification ships a worked intra India example with an IN origin postal code, an IN destination postal code, COD in shipmentSpecialServices.specialServiceTypes and INR customs values, which confirms that intra India cash on delivery is a rateable FedEx service through the API rather than only through a sales contact. There is no marketplace, no seller base and no catalogue here. One FedEx connection is one shipping account number, and a brand with a separate account for exports and for domestic will be two connections in our database.
API access
Anyone with a FedEx account can self serve. The sequence is:
- Create a login on developer.fedex.com.
- Create a project. A project is the credential container. You choose which APIs the project uses (Ship, Track, Rate and Transit Times, Pickup, Address Validation, Service Availability, Locations, Freight LTL, Open Ship, Trade Documents Upload, Ground End of Day Close).
- The project immediately issues a test API key and secret that work against
https://apis-sandbox.fedex.com, paired with a FedEx supplied test shipping account number. - To get production credentials you attach your real FedEx account number to the project and request production access from the portal. FedEx gates label producing APIs more tightly than read only ones. The exact certification steps and any label review are stated on the portal behind the login and could not be fetched on 2026-09-21, so treat the promotion process as medium confidence and confirm with the FedEx account team at onboarding.
There is no published fee for API access itself; you pay for shipments at your negotiated account rate. API versions are carried in the path (/v1/) rather than in a header, and the specification files are all 1.0.0 at the time of writing. FedEx does not publish first party language SDKs for the REST APIs the way Shopify or Amazon do: the portal provides OpenAPI specification files and Postman collections per API, and integrators generate clients from those. Mature open source clients exist, for example the FedEx connector in the Karrio multi carrier project, and are useful as reference implementations.
The older FedEx Web Services SOAP toolkit (WSDL based, fedex.com/ws/ship/v28 style endpoints, with a WebAuthenticationDetail block carrying key, password, account number and meter number) is a separate and older product. FedEx keeps a Developer Resource Center link for existing Web Services and FedEx Ship Manager Server customers, which is visible even on the unauthenticated portal page. Build new work on the REST APIs. Do not mix the two credential models: the REST APIs do not use a meter number.
Authentication
OAuth 2.0 client credentials, with no user consent step and no refresh token. You exchange the project key and secret for a bearer token, cache it, and mint a new one when it expires.
POST /oauth/tokenon the sandbox or production host withContent-Type: application/x-www-form-urlencoded.- Send
grant_type,client_id(the project API key) andclient_secret(the project secret key). - Read
access_tokenandexpires_infrom the response. - Send
Authorization: Bearer <access_token>on every subsequent call. - Re-mint the token before 3600 seconds elapse. FedEx states the token expires in 3600 seconds and "needs to be regenerated after every 60 minutes". There is no refresh grant: you repeat step 1.
Grant types, from the official authorization documentation:
The child_key and child_secret are described as the customer key and customer password returned by the Credential Registration API. That is the multi account model: a platform registers each of its merchants' FedEx accounts under its own parent credential and then mints a token scoped to that child. For our connector, a brand that hands us its own key and secret is the client_credentials case, one credential row per FedEx account; becoming a Compatible Solution Provider is a commercial conversation with FedEx and is out of scope until volume justifies it.
Token request
curl -X POST "https://apis-sandbox.fedex.com/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=l7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -d "client_secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX......",
"token_type": "bearer",
"expires_in": 3600,
"scope": "CXS"
}
Authenticated call
Every business API takes the same header set. content-type and authorization are required; x-customer-transaction-id is an optional client generated correlation id that FedEx echoes back as customerTransactionId, and x-locale selects the language and country for human readable strings.
curl -X POST "https://apis-sandbox.fedex.com/track/v1/trackingnumbers" \
-H "content-type: application/json" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX......" \
-H "x-customer-transaction-id: 624deea6-b709-470c-8c39-4b5511281492" \
-H "x-locale: en_US" \
-d '{"includeDetailedScans": true, "trackingInfo": [{"trackingNumberInfo": {"trackingNumber": "128667043726"}}]}'
Hosts:
Both hosts appear as the two servers entries in every FedEx OpenAPI specification file, so the same paths work in both environments and only the host and the key change.
Objects we can read
FedEx is a carrier. Of the unified model objects, only shipments is natively present, with returns partially present through return tags. There are no orders, order items, products, listings, inventory, customers or settlements in these APIs.
Orders
Not available. FedEx has no order object. Order context lives in our own database or in the channel connector, and reaches FedEx only as free text in customerReferences (types include CUSTOMER_REFERENCE, INVOICE, PURCHASE_ORDER, DEPARTMENT_NUMBER) on the shipment. Those same reference values are the key for POST /track/v1/referencenumbers, so writing our order_id into CUSTOMER_REFERENCE at ship time is what makes order to shipment reconciliation possible later.
Shipments and tracking
The Track API is the main read. It is documented by FedEx as "Basic Integrated Visibility" and accepts up to 30 shipments per request.
Key parameters on POST /track/v1/trackingnumbers:
includeDetailedScans(boolean).falsereturns only the latest status.truereturns the fullscanEventsarray. Set it totruefor our ingest; the whole point is the event history.trackingInfo[].trackingNumberInfo.trackingNumber, plus optionalcarrierCode(FDXEExpress,FDXGGround and similar) andtrackingNumberUniqueIdto disambiguate a number that has been reused.trackingInfo[].shipDateBeginandshipDateEndnarrow an ambiguous tracking number to a date window.
There is no pagination. The unit of work is up to 30 tracking numbers per call, so batch open AWBs 30 at a time.
Request:
{
"includeDetailedScans": true,
"trackingInfo": [
{
"trackingNumberInfo": {
"trackingNumber": "128667043726",
"carrierCode": "FDXE",
"trackingNumberUniqueId": "245822~123456789012~FDEG"
},
"shipDateBegin": "2020-03-29",
"shipDateEnd": "2020-04-01"
}
]
}
Response, trimmed to one tracking number and one scan event. Addresses here are PII.
{
"transactionId": "624deea6-b709-470c-8c39-4b5511281492",
"customerTransactionId": "AnyCo_order123456789",
"output": {
"completeTrackResults": [
{
"trackingNumber": "128667043726",
"trackResults": [
{
"trackingNumberInfo": { "trackingNumber": "128667043726", "carrierCode": "FDXE" },
"latestStatusDetail": {
"code": "PU",
"derivedCode": "PU",
"statusByLocale": "Picked up",
"description": "Picked up",
"scanLocation": { "city": "SEATTLE", "stateOrProvinceCode": "WA", "countryCode": "US" },
"ancillaryDetails": [ { "reason": "15", "reasonDescription": "Customer not available or business closed" } ],
"delayDetail": { "type": "WEATHER", "subType": "SNOW", "status": "DELAYED" }
},
"serviceDetail": { "type": "FEDEX_FREIGHT_ECONOMY", "shortDescription": "FL" },
"scanEvents": [
{
"date": "2018-02-02T12:01:00-07:00",
"eventType": "PU",
"eventDescription": "Picked Up",
"derivedStatus": "Picked Up",
"derivedStatusCode": "PU",
"exceptionCode": "A25",
"exceptionDescription": "Package available for clearance",
"locationId": "SEA",
"locationType": "CUSTOMS_BROKER",
"scanLocation": { "city": "SEATTLE", "stateOrProvinceCode": "WA", "countryCode": "US" }
}
],
"dateAndTimes": [ { "type": "ACTUAL_DELIVERY", "dateTime": "2007-09-27T00:00:00" } ],
"deliveryDetails": {
"receivedByName": "Reciever",
"signedByName": "Reciever",
"deliveryAttempts": "0",
"locationDescription": "Receptionist/Front Desk",
"actualDeliveryAddress": { "city": "SEATTLE", "postalCode": "98101", "countryCode": "US" }
},
"packageDetails": {
"count": "1",
"packagingDescription": { "type": "FEDEX_PAK" },
"declaredValue": { "currency": "USD", "value": 56.8 },
"weightAndDimensions": { "weight": [], "dimensions": [] }
},
"estimatedDeliveryTimeWindow": {
"type": "ESTIMATED_DELIVERY",
"window": { "begins": "2021-10-01T08:00:00", "ends": "2021-10-15T00:00:00-06:00" }
},
"recipientInformation": { "address": { "city": "SEATTLE", "countryCode": "US" } },
"availableImages": [ { "type": "BILL_OF_LADING", "size": "LARGE" } ],
"error": null
}
]
}
],
"alerts": []
}
}
Notes for the ingest:
errorsits insidetrackResults, not at the top level. A batch of 30 can be partly successful: an unknown number comes back witherror.codesuch asTRACKING.TRACKINGNUMBER.EMPTYand a human message, while its siblings succeed. Do not treat a 200 as an all clear.latestStatusDetail.derivedCodeplusscanEvents[].eventTypeare the two status vocabularies. Map on the code, not on the English text, becausestatusByLocaleanddescriptionchange withx-locale.delayDetail(type,subType,statusofON_TIME,EARLYorDELAYED) is the closest thing FedEx gives to a structured exception reason, alongsideexceptionCodeandexceptionDescriptionon each scan andancillaryDetails.reasonon the latest status. Those are what feed ourndr_reason.dateAndTimes[]is a typed list, not fixed keys.ACTUAL_DELIVERY,ACTUAL_PICKUP,SHIPand similar types each appear as an entry, so select bytype.
Proof of delivery
POST /track/v1/trackingdocuments returns a document for a tracked shipment. FedEx documents the available documentType values as SIGNATURE_PROOF_OF_DELIVERY, BILL_OF_LADING and FREIGHT_BILLING_DOCUMENT, in PDF or PNG. The availableImages[] array on a track result tells you in advance which document types and sizes exist, so check it before requesting.
Rates, serviceability and transit time
POST /rate/v1/rates/quotes is a single endpoint that answers three questions at once: is this lane serviceable, what will it cost, and when will it arrive. FedEx states plainly that the Rate API does not quote FedEx Freight services; freight has its own API.
Control parameters sit on rateRequestControlParameters: returnTransitTimes (set true to get commit dates and transit time bands back with the price), servicesNeededOnRateFailure (returns the list of services that would have worked when the requested one fails), rateSortOrder and variableOptions.
On requestedShipment, the fields that decide the answer are shipper.address, recipient.address, serviceType (omit it to rate shop all services), packagingType, pickupType, shipDateStamp, requestedPackageLineItems[].weight, preferredCurrency, and rateRequestType, an array of LIST, ACCOUNT, PREFERRED or INCENTIVE. LIST is published pricing and ACCOUNT is the negotiated rate, and asking for both in one call is how you show a merchant their discount.
There is no separate pincode serviceability endpoint. Serviceability is a rate call that either returns services or does not. For pure postal code sanity checking there is POST /country/v1/postal/validate in the Postal Code Validation API, and for full address checking POST /address/v1/addresses/resolve.
Official intra India cash on delivery example, taken verbatim from the FedEx Rate API specification:
{
"accountNumber": { "value": "XXXXX7364" },
"requestedShipment": {
"shipper": { "address": { "postalCode": 500001, "countryCode": "IN" } },
"recipient": { "address": { "postalCode": 600021, "countryCode": "IN" } },
"pickupType": "DROPOFF_AT_FEDEX_LOCATION",
"packagingType": "YOUR_PACKAGING",
"shipmentSpecialServices": { "specialServiceTypes": ["COD"] },
"rateRequestType": ["LIST", "ACCOUNT"],
"customsClearanceDetail": {
"dutiesPayment": { "paymentType": "SENDER" },
"commercialInvoice": { "shipmentPurpose": "SOLD" },
"freightOnValue": "CARRIER_RISK",
"commodities": [ { "description": "Camera", "weight": { "value": 1, "units": "KG" }, "quantity": 1, "quantityUnits": "PCS", "customsValue": { "amount": 100, "currency": "INR" } } ]
},
"requestedPackageLineItems": [ { "weight": { "units": "KG", "value": 1 } } ]
}
}
Response, trimmed to one service and with surcharge and tax arrays emptied:
{
"transactionId": "624deea6-b709-470c-8c39-4b5511281492",
"output": {
"rateReplyDetails": [
{
"serviceType": "FEDEX_GROUND",
"serviceName": "FedEx Ground",
"packagingType": "YOUR_PACKAGING",
"ratedShipmentDetails": [
{
"rateType": "ACCOUNT",
"ratedWeightMethod": "ACTUAL",
"totalBaseCharge": 445.54,
"totalDiscounts": 445.54,
"totalNetCharge": 445.54,
"totalDutiesAndTaxes": 445.54,
"totalNetChargeWithDutiesAndTaxes": 445.54,
"shipmentRateDetail": {
"currency": "USD",
"rateZone": "CA003O",
"ratingBasis": "SHIPMENT_WEIGHT_BASED",
"pricingCode": "ACTUAL",
"totalSurcharges": 586.25,
"fuelSurchargePercent": 10.5,
"dimDivisor": 10,
"surCharges": [],
"taxes": []
}
}
],
"operationalDetail": {
"transitTime": "THREE_DAYS",
"commitDate": "2019-07-22T08:30:00",
"deliveryDate": "2019-07-22T08:30:00",
"deliveryDay": "SAT",
"ineligibleForMoneyBackGuarantee": false
}
}
]
}
}
Pickup
Availability returns cutoff time, pickup date, access time and ready time for an address, carrier and request type. Create takes associatedAccountNumber, originDetail (with readyDateTimestamp, customerCloseTime, pickupDateType of SAME_DAY or FUTURE_DAY, packageLocation), carrierCode, packageCount, totalWeight and optional remarks.
{
"associatedAccountNumber": { "value": "XXXXX7364" },
"originDetail": {
"pickupAddressType": "ACCOUNT",
"readyDateTimestamp": "2020-04-02T11:00:00Z",
"customerCloseTime": "18:00:00",
"pickupDateType": "SAME_DAY",
"packageLocation": "FRONT",
"earlyPickup": false
},
"associatedAccountNumberType": "FEDEX_GROUND",
"totalWeight": { "units": "KG", "value": 20 },
"packageCount": 5,
"carrierCode": "FDXE",
"remarks": "Please ring bell at loading dock.",
"countryRelationships": "DOMESTIC"
}
{
"transactionId": "624deea6-b709-470c-8c39-4b5511281492",
"output": { "pickupConfirmationCode": "3001", "message": "Courier on the way", "location": "COSA", "alerts": [] }
}
Store pickupConfirmationCode and, for Express, location. Cancellation needs both, plus scheduledDate and carrierCode.
Address validation
POST /address/v1/addresses/resolve normalises and classifies an address before we ship, which is the cheapest way to avoid an undelivered attempt.
{
"validateAddressControlParameters": { "includeResolutionTokens": true },
"addressesToValidate": [
{
"clientReferenceId": "None",
"address": {
"streetLines": ["7372 PARKRIDGE BLVD", "APT 286"],
"city": "IRVING",
"stateOrProvinceCode": "TX",
"postalCode": "75063-8659",
"countryCode": "US"
}
}
]
}
{
"transactionId": "XXX_ORDERXXXX789",
"output": {
"resolvedAddresses": [
{
"city": "IRVING",
"stateOrProvinceCode": "TX",
"countryCode": "US",
"classification": "BUSINESS",
"postOfficeBox": true,
"normalizedStatusNameDPV": true,
"resolutionMethodName": "USPS_VALIDATE",
"generalDelivery": false
}
],
"alerts": [
{ "code": "SHIP.RECIPIENT.POSTALCITY.MISMATCH", "message": "Recipient Postal-City Mismatch.", "alertType": "NOTE" }
]
}
}
classification of BUSINESS against RESIDENTIAL is worth persisting: it changes the rate on Ground and Home Delivery.
Returns and cancellations
Partial. FedEx exposes returns as label operations rather than as a return object with a reason and a refund:
POST /ship/v1/shipments/tagcreates a return tag, which is a courier collected return.PUT /ship/v1/shipments/tag/cancel/{shipmentid}cancels it.- A return shipment can also be created through the normal Ship endpoint with
shipmentSpecialServices.returnShipmentDetailset. - The track result carries a
returnDetailblock andPOST /track/v1/associatedshipmentslinks an outbound to its return.
There is no NDR or RTO object. A failed attempt shows up as a scan event with an exceptionCode and ancillaryDetails.reason plus an action string, and an RTO shows up as a new tracking number moving in the reverse direction. Our connector has to derive both from the event stream.
Payments and settlements
Not available in these APIs. FedEx billing and invoice data is a separate enterprise product (invoice files and the FedEx Billing Online portal), not part of the developer portal catalogue read here. Cash on delivery amounts are configured on the way out (shipmentSpecialServices.shipmentCODDetail carries codCollectionType of CASH and similar, remitToName, codRecipient and a financialInstitutionContactAndAddress; per package the amount sits at packageCODDetail.codCollectionAmount), and the collected sum appears on the ship response as pieceResponses[].codCollectionAmount. But there is no COD remittance API: reconciling money actually received against shipments is a finance file exercise outside this catalogue.
Customers, products, listings, inventory, locations
Not applicable. The only location concept is FedEx's own network: the Location Search API finds FedEx staffed locations and drop off points, and track results carry originLocation, destinationLocation and holdAtLocation. None of those are seller warehouses.
Writing back: listings, price and stock
Not applicable, this is a carrier. There are no listings, prices or stock levels at FedEx and nothing in this catalogue writes to a sales channel. The write path is shipment lifecycle.
Create a shipment
POST /ship/v1/shipments is the call that produces an AWB and a label. Required inputs, per the FedEx Ship documentation: account number, pickup type, service type, packaging type, shipper information, recipient information, shipping payment type, individual package weights and a label specification.
Shape of the request body, abbreviated to the fields that matter:
{
"mergeLabelDocOption": "LABELS_AND_DOCS",
"accountNumber": { "value": "XXXXX7364" },
"labelResponseOptions": "LABEL",
"requestedShipment": {
"shipDatestamp": "2019-10-14",
"pickupType": "USE_SCHEDULED_PICKUP",
"serviceType": "PRIORITY_OVERNIGHT",
"packagingType": "YOUR_PACKAGING",
"totalWeight": 20.6,
"shipper": {},
"recipients": [ {} ],
"shippingChargesPayment": { "paymentType": "SENDER", "payor": {} },
"labelSpecification": {
"labelFormatType": "COMMON2D",
"imageType": "PDF",
"labelStockType": "PAPER_7X475",
"labelOrder": "SHIPPING_LABEL_FIRST",
"resolution": 300
},
"shipmentSpecialServices": {
"specialServiceTypes": ["COD"],
"shipmentCODDetail": { "codCollectionType": "CASH", "remitToName": "Acme Brands Pvt Ltd" }
},
"emailNotificationDetail": { "aggregationType": "PER_PACKAGE", "emailNotificationRecipients": [] },
"blockInsightVisibility": true,
"customsClearanceDetail": {},
"requestedPackageLineItems": []
}
}
The response is where the AWB and the label bytes come back:
{
"transactionId": "624deea6-b709-470c-8c39-4b5511281492",
"customerTransactionId": "AnyCo_order123456789",
"output": {
"transactionShipments": [
{
"serviceType": "STANDARD_OVERNIGHT",
"serviceName": "FedEx 2 Day Freight",
"serviceCategory": "EXPRESS",
"shipDatestamp": "2010-03-04",
"masterTrackingNumber": "794953535000",
"pieceResponses": [
{
"trackingNumber": "794953535000",
"masterTrackingNumber": "794953535000",
"packageSequenceNumber": 215,
"trackingIdType": "FEDEX",
"baseRateAmount": 321.45,
"netChargeAmount": 21.45,
"netDiscountAmount": 121.45,
"listRateAmount": 1.45,
"codCollectionAmount": 231.45,
"customerReferences": [ { "customerReferenceType": "DEPARTMENT_NUMBER", "value": "3686" } ],
"packageDocuments": [
{ "contentType": "COMMERCIAL_INVOICE", "docType": "PDF", "encodedLabel": "encoded label", "url": "https://.../document/v2/document/retrieve/SH,794810209259_SHIPPING_P/isLabel=true" }
]
}
],
"shipmentDocuments": [
{ "contentType": "COMMERCIAL_INVOICE", "docType": "PDF", "copiesToPrint": 1, "encodedLabel": "encoded label" }
],
"alerts": []
}
],
"alerts": [],
"jobId": "abc123456"
}
}
Two things to build around. First, the label arrives either as encodedLabel, a base64 string to decode and store, or as a url to fetch, depending on labelResponseOptions. Second, jobId is present because FedEx can process a shipment asynchronously; when it does, you poll POST /ship/v1/shipments/results with { "accountNumber": {...}, "jobId": "..." } until the shipment documents appear.
Validate before you ship
POST /ship/v1/shipments/packages/validate checks a shipment request without creating anything, and returns alerts such as SHIPMENT.VALIDATION.SUCCESS or RECIPIENTCONTACT.PHONENUMBER.INVALID. Cheap insurance before spending a label. Separately, POST /ship/v1/endofday/ and PUT /ship/v1/endofday/ in the Ground End of Day Close API produce the end of day manifest for Ground shipments: Ground labels are not considered tendered until the close runs, so a connector that creates Ground labels must also run the close.
Cancel a shipment
PUT /ship/v1/shipments/cancel, keyed on the tracking number, with deletionControl choosing between deleting one package and the whole shipment.
{
"accountNumber": { "value": "XXXXX7364" },
"trackingNumber": "794953555571",
"senderCountryCode": "IN",
"deletionControl": "DELETE_ALL_PACKAGES",
"emailShipment": "false"
}
{
"transactionId": "624deea6-b709-470c-8c39-4b5511281492",
"output": { "cancelledShipment": true, "cancelledHistory": true, "message": "Shipment is successfully cancelled" }
}
Webhooks and notifications
There is no HTTP push in the public FedEx REST catalogue. This is the single biggest design constraint of the connector, and it is easy to misread the documentation here.
POST /track/v1/notifications is called "Send Notification", and it does subscribe to shipment events, but the delivery channel is email to a person, not a callback to a server. The request body carries senderEMailAddress, senderContactName, a trackingNumberInfo, and a trackingEventNotificationDetail.trackingNotifications[] array where each entry has a role (SHIPPER, RECIPIENT, BROKER, OTHER), a notificationDetail containing an emailDetail with emailAddress and name, a notificationType of HTML, TEXT or similar, a localization, and a notificationEventTypes array. FedEx documents the event types as ON_DELIVERY, ON_ESTIMATED_DELIVERY, ON_EXCEPTION and ON_TENDER, and caps it at four email recipients.
So the ingest is a poll, and the practical design is:
- Poll
POST /track/v1/trackingnumberswithincludeDetailedScans: true, 30 tracking numbers per call. - Cadence by shipment age: every 2 to 4 hours for a shipment in transit, hourly on the expected delivery date and the day after, then stop once
latestStatusDetail.derivedCodereaches a terminal state (delivered, returned) or the shipment is older than about 45 days. - De-duplicate on the tuple of tracking number,
scanEvents[].date,eventTypeandlocationId, since the whole history is returned on every call and a replayed event must not become a second row inshipments.events[]. - A parallel email address can be subscribed through the notification endpoint if a human wants alerts, but do not build the data pipeline on parsing those emails.
If a merchant needs true push, that is an enterprise conversation with FedEx about visibility products that are not in the self serve portal. Treat "FedEx has webhooks" as unverified until an account manager confirms otherwise in writing.
Rate limits and pagination
FedEx does not publish numeric rate limits on the unauthenticated portal, and the guide pages that would state them returned errors or 404s to the fetcher on 2026-09-21. What is verifiable:
Practical cadence that stays well clear of trouble: one token per hour per account, tracking polls batched 30 at a time on the schedule above, rate calls made at quote time rather than on a timer, and a single retry with exponential backoff on 500 and 503. Treat 401 as "mint a new token and retry once", not as a permanent failure, because a cached token that expired mid batch is the common cause. Errors come back with an HTTP status of 400, 401, 403, 404, 500 or 503 and a body containing transactionId and an errors[] array of { code, message, parameterList }. Codes are dotted and stable, for example TRACKING.TRACKINGNUMBER.EMPTY, which makes them safe to branch on.
Mapping to the unified model
FedEx populates shipments and nothing else. The rest of the table records where the unified field has to come from our own data.
Gaps and open questions
- Numeric rate limits are not published on the pages reachable without a login. Ask the FedEx account team for the per project transactions per second and daily ceiling before designing the polling schedule for a large book of shipments.
- Production promotion and label certification. The portal states that test credentials come first and production credentials are requested separately, but the exact certification steps for label producing APIs could not be fetched. Confirm before promising a merchant a go live date.
- No webhook. Verify with FedEx whether any push option exists under a Compatible Solution Provider or enterprise visibility agreement. Until then the connector is poll only.
- No COD remittance API and no billing or invoice API in this catalogue. Confirm how a merchant actually receives their intra India COD money and in what file format, and expect
shipping_costin our database to be the rated estimate rather than the invoiced amount, with drift after weight and dimension audits. - Multi account model. Whether we go
client_credentialsper merchant or become a Compatible Solution Provider withcsp_credentialsand the Credential Registration API changes the credential store design substantially. That Credential Registration API is referenced in the authorization documentation but was not readable without a login, so its request and response shapes are unverified. - Specification provenance. The OpenAPI files used here are FedEx authored but were read from a third party mirror. Re-download them from the portal after login and diff before writing code.
Sources
- FedEx Developer Portal (landing pages reachable, catalogue and several guide pages returned errors or 404 to an unauthenticated fetcher on 2026-09-21)
- FedEx API Authorization documentation (grant types, token fields, 3600 second lifetime, sample response)
- FedEx Ship API documentation (operations, required fields) and Pickup Request API documentation (three operations, access time, cutoff)
- FedEx Track API documentation (30 shipments per request, reference types, document types, notification event types, four recipient cap)
- FedEx Rates and Transit Times API documentation (rate request types, freight exclusion, control parameters)
- Official FedEx OpenAPI specification files for Ship, Basic Integrated Visibility (Track), Rates and Transit Times, Pickup Request, Address Validation, Postal Code Validation, Service Availability and Ground End of Day Close, read on 2026-09-21 from the ToolJet FedEx marketplace plugin mirror. These supplied every endpoint path, server host, field name and sample value quoted above
- Karrio FedEx connector, an open source reference implementation of the same endpoints