Skip to main content
RapidDev - Software Development Agency
v0-integrationsNext.js API Route

How to Integrate FedEx API with V0

To integrate the FedEx API with V0 by Vercel, generate a shipping UI with V0, create Next.js API routes that authenticate with FedEx's OAuth2 client credentials flow and call rate quote, tracking, and shipment endpoints, store your credentials in Vercel environment variables, and deploy. Your app can get shipping rates, track packages, and generate labels entirely server-side.

What you'll learn

  • How to generate a shipping rate calculator or tracking UI with V0
  • How to implement FedEx OAuth2 client credentials authentication in a Next.js API route
  • How to fetch shipping rate quotes for different service types from the FedEx Rates and Transit API
  • How to track shipments and parse FedEx tracking scan events
  • How to store FedEx API credentials securely in Vercel environment variables
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read45 minutesOtherMarch 2026RapidDev Engineering Team
TL;DR

To integrate the FedEx API with V0 by Vercel, generate a shipping UI with V0, create Next.js API routes that authenticate with FedEx's OAuth2 client credentials flow and call rate quote, tracking, and shipment endpoints, store your credentials in Vercel environment variables, and deploy. Your app can get shipping rates, track packages, and generate labels entirely server-side.

Add FedEx Shipping Rates, Tracking, and Labels to V0-Generated Apps

The FedEx REST API (released 2022, with the older SOAP API being retired) provides programmatic access to FedEx's full logistics capabilities: rate quotes for all FedEx service types, real-time shipment tracking, label generation, and address validation. For e-commerce applications, embedded marketplaces, or any app that needs to quote or fulfill physical shipments, FedEx API integration adds significant value. V0 generates the shipping UI — rate comparison tables, tracking status pages, shipment forms — and API routes handle the FedEx communication securely.

FedEx API authentication differs from simple API key auth. It uses OAuth2 client credentials flow: you exchange your client ID and client secret for a short-lived access token (expiring in one hour), then include that Bearer token in all subsequent API requests. This means your API routes need to handle token lifecycle: obtain a token, cache it with its expiry timestamp, and refresh it before expiry. Implementing this correctly prevents unnecessary token requests on every API call while ensuring tokens are always valid.

Before your app goes live, FedEx requires moving from their sandbox environment (developer testing account) to production credentials. The sandbox uses test account numbers and doesn't generate real shipments or charges — perfect for development. The transition to production requires a FedEx agreement and account number. Most developers start in sandbox, build the full integration, then request production access.

Integration method

Next.js API Route

The FedEx REST API integrates with V0-generated Next.js apps through server-side API routes that handle OAuth2 client credentials authentication and proxy shipping requests to FedEx endpoints. Your FedEx API key and secret are stored as server-only Vercel environment variables. The OAuth2 access token is obtained server-side, cached to avoid redundant token requests, and used to authenticate all FedEx API calls within your API routes.

Prerequisites

  • A FedEx Developer account — register at developer.fedex.com and create a new project to get your API key and secret
  • FedEx sandbox account credentials: client ID (API key) and client secret from your FedEx Developer Portal project
  • A FedEx account number — available from your FedEx customer account, used as the payor account for rate quotes and shipments
  • A V0 account at v0.dev to generate the shipping UI
  • A Vercel account to deploy and manage environment variables

Step-by-step guide

1

Generate the Shipping UI with V0

Open V0 at v0.dev and describe the shipping interface you need. FedEx integrations commonly need one of three UI patterns: a rate calculator (form inputs → comparison table), a tracking page (tracking number input → timeline), or a shipment creation form (order details → label download). Be specific in your V0 prompt about the inputs users will provide (ZIP codes, dimensions, weight, tracking number) and the outputs to display (service types, prices, delivery estimates, event logs). V0 will generate React components using shadcn/ui and Tailwind. The rate comparison component will likely render a table or card grid — note how V0 structures the mock rate data objects since your API route needs to return matching shapes. For the tracking timeline, V0 works well with a vertical list component where each item has a timestamp, location, and description. Push the generated project to GitHub via V0's Git panel. At this stage the components show mock data — the next steps connect them to real FedEx API responses.

V0 Prompt

Create a shipping rate calculator page with a two-column form: left side has Origin ZIP and Destination ZIP inputs; right side has Weight (lbs), Length, Width, Height (inches) inputs. A 'Get Rates' button at the bottom triggers the calculation. Results appear below as a sortable table: Service, Transit Time, Estimated Delivery, Rate — with a 'Select' button on each row. Add a loading skeleton and an error state. Post to /api/fedex/rates. Clean professional design with muted purple and orange FedEx-inspired accents.

Paste this in V0 chat

Pro tip: Ask V0 to include basic form validation — ZIP code format (5 digits), positive weight, positive dimensions — before the form submits to the API. This prevents unnecessary API calls for obviously invalid input and saves FedEx API quota.

Expected result: A styled shipping rate calculator or tracking page renders in V0's preview with form inputs, a results table with mock rates, and loading/error states — ready to receive real FedEx API data.

2

Create the FedEx OAuth2 Token Helper

FedEx API authentication requires obtaining an OAuth2 access token before every set of API calls. Create a token management module at lib/fedex-auth.ts that handles the token lifecycle. The token endpoint is https://apis-sandbox.fedex.com/oauth/token (sandbox) or https://apis.fedex.com/oauth/token (production). You POST your client_id and client_secret as application/x-www-form-urlencoded with grant_type=client_credentials. FedEx returns an access_token valid for 3600 seconds (1 hour). Rather than requesting a new token on every API call, cache the token in a module-level variable with its expiry timestamp. The token helper checks if the cached token is still valid (with a 60-second safety margin) before deciding whether to request a new one. In Vercel's serverless environment, module-level variables persist within the same function instance during a request but are not shared between instances — this caching still provides value by avoiding redundant token requests within a single request that makes multiple FedEx API calls. For production, you might store tokens in Redis/Upstash if you need cross-instance caching.

lib/fedex-auth.ts
1// lib/fedex-auth.ts
2interface CachedToken {
3 accessToken: string;
4 expiresAt: number; // Unix timestamp in ms
5}
6
7let tokenCache: CachedToken | null = null;
8
9function getFedexBaseUrl(): string {
10 return process.env.FEDEX_SANDBOX === 'true'
11 ? 'https://apis-sandbox.fedex.com'
12 : 'https://apis.fedex.com';
13}
14
15export async function getFedexAccessToken(): Promise<string> {
16 const now = Date.now();
17 const bufferMs = 60 * 1000; // 60 second safety margin
18
19 // Return cached token if still valid
20 if (tokenCache && tokenCache.expiresAt > now + bufferMs) {
21 return tokenCache.accessToken;
22 }
23
24 const clientId = process.env.FEDEX_CLIENT_ID;
25 const clientSecret = process.env.FEDEX_CLIENT_SECRET;
26
27 if (!clientId || !clientSecret) {
28 throw new Error('FedEx credentials not configured');
29 }
30
31 const body = new URLSearchParams({
32 grant_type: 'client_credentials',
33 client_id: clientId,
34 client_secret: clientSecret,
35 });
36
37 const response = await fetch(`${getFedexBaseUrl()}/oauth/token`, {
38 method: 'POST',
39 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
40 body: body.toString(),
41 cache: 'no-store',
42 });
43
44 if (!response.ok) {
45 const error = await response.text();
46 throw new Error(`FedEx token request failed: ${response.status} ${error}`);
47 }
48
49 const data = await response.json();
50 tokenCache = {
51 accessToken: data.access_token,
52 expiresAt: now + (data.expires_in * 1000),
53 };
54
55 return tokenCache.accessToken;
56}
57
58export async function fedexFetch(path: string, options: RequestInit = {}): Promise<Response> {
59 const token = await getFedexAccessToken();
60 const baseUrl = getFedexBaseUrl();
61
62 return fetch(`${baseUrl}${path}`, {
63 ...options,
64 headers: {
65 ...options.headers,
66 Authorization: `Bearer ${token}`,
67 'Content-Type': 'application/json',
68 'X-locale': 'en_US',
69 },
70 });
71}

Pro tip: Set FEDEX_SANDBOX=true in Vercel's Preview environment variables and FEDEX_SANDBOX=false (or leave unset) for Production. This lets you run sandbox testing on preview deployments while production uses live FedEx credentials automatically.

Expected result: The lib/fedex-auth.ts module exports a fedexFetch helper that automatically obtains and caches OAuth2 tokens, which API routes can use to make authenticated FedEx requests without managing tokens themselves.

3

Create the Rate Quote and Tracking API Routes

Create the API routes for FedEx rate quotes and tracking using the fedexFetch helper. The FedEx Rates and Transit Times API endpoint is /rate/v1/rates/quotes (POST). The request body specifies the shipper address, recipient address, package dimensions and weight, and requested service types. The response includes a list of rate replies, each containing the service type, total net charge, and transit time. Your API route transforms this into a clean array matching your dashboard component's expected format. For tracking, the FedEx Track API endpoint is /track/v1/trackingnumbers (POST). Send an array containing the tracking number and get back shipment events with status codes, timestamps, and location details. The FedEx API uses specific codes for service types (FEDEX_GROUND, FEDEX_2_DAY, STANDARD_OVERNIGHT, etc.) and event types — map these to human-readable labels in your transformation. FedEx's request schemas are detailed — pay close attention to required vs optional fields. The account number (FEDEX_ACCOUNT_NUMBER) appears in the rate request as the shipper's account and payment method.

app/api/fedex/rates/route.ts
1// app/api/fedex/rates/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { fedexFetch } from '@/lib/fedex-auth';
4
5const SERVICE_LABELS: Record<string, string> = {
6 FEDEX_GROUND: 'FedEx Ground',
7 FEDEX_2_DAY: 'FedEx 2Day',
8 FEDEX_2_DAY_AM: 'FedEx 2Day AM',
9 STANDARD_OVERNIGHT: 'FedEx Standard Overnight',
10 PRIORITY_OVERNIGHT: 'FedEx Priority Overnight',
11 FIRST_OVERNIGHT: 'FedEx First Overnight',
12 FEDEX_EXPRESS_SAVER: 'FedEx Express Saver',
13};
14
15export async function POST(request: NextRequest) {
16 try {
17 const { originZip, destinationZip, weight, length, width, height } =
18 await request.json();
19
20 const accountNumber = process.env.FEDEX_ACCOUNT_NUMBER;
21 if (!accountNumber) {
22 return NextResponse.json({ error: 'FedEx account not configured' }, { status: 500 });
23 }
24
25 const rateRequest = {
26 accountNumber: { value: accountNumber },
27 requestedShipment: {
28 shipper: { address: { postalCode: originZip, countryCode: 'US' } },
29 recipient: { address: { postalCode: destinationZip, countryCode: 'US' } },
30 pickupType: 'DROPOFF_AT_FEDEX_LOCATION',
31 rateRequestType: ['ACCOUNT', 'LIST'],
32 requestedPackageLineItems: [{
33 weight: { units: 'LB', value: weight },
34 dimensions: { length, width, height, units: 'IN' },
35 }],
36 },
37 };
38
39 const response = await fedexFetch('/rate/v1/rates/quotes', {
40 method: 'POST',
41 body: JSON.stringify(rateRequest),
42 });
43
44 if (!response.ok) {
45 const error = await response.json();
46 console.error('FedEx rate error:', error);
47 return NextResponse.json({ error: 'Rate quote failed' }, { status: response.status });
48 }
49
50 const data = await response.json();
51 const rateDetails = data.output?.rateReplyDetails || [];
52
53 const rates = rateDetails.map((detail: Record<string, unknown>) => {
54 const shipmentDetail = (detail.ratedShipmentDetails as Record<string, unknown>[])?.[0];
55 const totalCharge = (shipmentDetail?.totalNetChargeWithDutiesAndTaxes as Record<string, unknown>)
56 || (shipmentDetail?.totalNetCharge as Record<string, unknown>);
57
58 return {
59 serviceType: detail.serviceType,
60 serviceName: SERVICE_LABELS[detail.serviceType as string] || detail.serviceType,
61 deliveryDate: detail.commit?.dateDetail?.dayFormat || 'N/A',
62 transitDays: detail.commit?.transitDays?.description || '',
63 rate: parseFloat((totalCharge?.amount as string) || '0'),
64 currency: totalCharge?.currency || 'USD',
65 };
66 }).sort((a: { rate: number }, b: { rate: number }) => a.rate - b.rate);
67
68 return NextResponse.json({ rates });
69 } catch (error) {
70 console.error('FedEx rates error:', error);
71 return NextResponse.json({ error: 'Failed to get rates' }, { status: 500 });
72 }
73}
74
75// app/api/fedex/track/route.ts
76import { NextRequest, NextResponse } from 'next/server';
77import { fedexFetch } from '@/lib/fedex-auth';
78
79export async function GET(request: NextRequest) {
80 const { searchParams } = new URL(request.url);
81 const trackingNumber = searchParams.get('trackingNumber');
82
83 if (!trackingNumber) {
84 return NextResponse.json({ error: 'trackingNumber is required' }, { status: 400 });
85 }
86
87 try {
88 const response = await fedexFetch('/track/v1/trackingnumbers', {
89 method: 'POST',
90 body: JSON.stringify({
91 includeDetailedScans: true,
92 trackingInfo: [{ trackingNumberInfo: { trackingNumber } }],
93 }),
94 });
95
96 if (!response.ok) {
97 return NextResponse.json({ error: 'Tracking request failed' }, { status: response.status });
98 }
99
100 const data = await response.json();
101 const trackResult = data.output?.completeTrackResults?.[0]?.trackResults?.[0];
102
103 if (!trackResult) {
104 return NextResponse.json({ error: 'No tracking data found' }, { status: 404 });
105 }
106
107 const scanEvents = (trackResult.scanEvents || []).map((event: Record<string, unknown>) => ({
108 timestamp: event.date,
109 eventType: event.eventType,
110 description: event.eventDescription,
111 location: [event.scanLocation?.city, event.scanLocation?.stateOrProvinceCode]
112 .filter(Boolean).join(', '),
113 }));
114
115 return NextResponse.json({
116 trackingNumber,
117 status: trackResult.latestStatusDetail?.description,
118 estimatedDelivery: trackResult.estimatedDeliveryTimeWindow?.window?.ends,
119 events: scanEvents,
120 });
121 } catch (error) {
122 console.error('FedEx tracking error:', error);
123 return NextResponse.json({ error: 'Tracking failed' }, { status: 500 });
124 }
125}

Pro tip: FedEx's sandbox environment uses test account numbers and won't charge you. Use FedEx's developer documentation sandbox to generate test tracking numbers you can use when testing the tracking API integration.

Expected result: POST /api/fedex/rates with origin/destination ZIP codes and package dimensions returns an array of shipping options with rates. GET /api/fedex/track?trackingNumber=XXX returns scan events and current status.

4

Configure FedEx Credentials in Vercel and Deploy

Push your project to GitHub and add FedEx credentials to Vercel. Open the Vercel Dashboard, navigate to your project → Settings → Environment Variables. Add four variables: FEDEX_CLIENT_ID (your API key from the FedEx Developer Portal project), FEDEX_CLIENT_SECRET (your project's secret key), FEDEX_ACCOUNT_NUMBER (your FedEx customer account number, found in your FedEx account profile), and FEDEX_SANDBOX (set to 'true' for Preview deployments and 'false' for Production). None of these should have NEXT_PUBLIC_ prefix since all FedEx calls are server-side. For Production deployments, make sure you've applied for and received live FedEx API credentials through the FedEx Developer Portal — sandbox credentials only work against the sandbox API URL. After saving variables and deploying, test the rate calculator with real ZIP codes. The FedEx sandbox returns realistic-looking rates but uses test data. Once you've verified the integration works in sandbox, request production credentials through the FedEx Developer Portal to go live with real shipment creation and charges.

Pro tip: For production shipment creation (generating real labels), you'll also need to add shipping address validation. The FedEx Address Validation API (/address/v1/addresses/resolve) can verify addresses before attempting shipment creation, reducing label creation failures.

Expected result: Vercel deployment succeeds with FedEx credentials injected. The shipping rate calculator returns real FedEx rate quotes in the deployed app, and the tracking page shows scan events for real tracking numbers.

Common use cases

Shipping Rate Calculator

A tool where users enter origin and destination ZIP codes plus package dimensions and weight to get real-time FedEx shipping quotes across all service types (Ground, 2Day, Overnight, etc.). Displays estimated delivery dates and prices in a comparison table.

V0 Prompt

Create a shipping rate calculator with inputs for origin ZIP, destination ZIP, package weight (lbs), length, width, height (inches), and a 'Get Rates' button. Show results in a comparison table with columns: Service Type, Delivery Date, List Rate, and an 'Use This Rate' button. Include a loading state while rates load from /api/fedex/rates. Clean white card design with FedEx purple accents.

Copy this prompt to try it in V0

Package Tracking Dashboard

A tracking page where users enter a FedEx tracking number to see the full shipment journey — scan events with locations and timestamps, current status, and estimated delivery. Can be embedded in an e-commerce order confirmation page.

V0 Prompt

Build a package tracking page with a tracking number input, a status badge showing current status (In Transit, Delivered, etc.) with an appropriate icon, a vertical timeline of scan events each showing date/time, location, and event description, and a summary card at the top showing estimated delivery and origin/destination. Data from /api/fedex/track. Modern timeline design with FedEx orange for active events.

Copy this prompt to try it in V0

E-commerce Checkout Shipping Options

A shipping options selector embedded in an e-commerce checkout flow. Fetches available FedEx services for the customer's address, displays prices and delivery estimates, and stores the selected service for order fulfillment.

V0 Prompt

Design a shipping options selector component for a checkout page showing 3-5 FedEx shipping options as radio button cards. Each card shows service name (Economy, Priority, Overnight), estimated delivery date, and price. Mark one as 'Best Value'. When a user selects an option and clicks Continue, post the selection to /api/fedex/select-rate. Loading skeleton while fetching from /api/fedex/rates. Minimal clean checkout style.

Copy this prompt to try it in V0

Troubleshooting

OAuth token request returns 401 or 'Invalid credentials'

Cause: The FEDEX_CLIENT_ID or FEDEX_CLIENT_SECRET is incorrect. FedEx credentials are case-sensitive and should be copied exactly from the Developer Portal. Also verify you're using sandbox credentials against the sandbox URL and production credentials against the production URL.

Solution: Re-copy credentials from the FedEx Developer Portal project page. Confirm FEDEX_SANDBOX is set correctly for each Vercel environment. Check that the grant_type is exactly 'client_credentials' and the request uses Content-Type: application/x-www-form-urlencoded, not JSON.

Rate quote API returns empty rateReplyDetails array

Cause: FedEx may not offer service to the requested destination postal code combination, the package dimensions or weight are outside FedEx's acceptable range, or the sandbox account doesn't have all service types enabled.

Solution: Test with well-known US ZIP codes (e.g., 10001 for New York to 90210 for Beverly Hills). Verify package weight is positive and dimensions are realistic. Check if FedEx Ground service is enabled on your account — not all accounts have all services. Review the raw FedEx response for specific error alerts in data.output.alerts.

typescript
1// Log FedEx alerts for debugging:
2const alerts = data.output?.alerts || [];
3console.log('FedEx alerts:', alerts);

Tracking returns 'No tracking data found' for a valid tracking number

Cause: The tracking number may be from a different carrier, the shipment hasn't been created yet in FedEx's system, or the tracking number format includes spaces or dashes that need to be stripped.

Solution: Strip all spaces and hyphens from tracking numbers before sending to the API: const cleanedNumber = trackingNumber.replace(/[\s-]/g, ''). Verify the tracking number is a FedEx tracking number (FedEx numbers are typically 12, 15, 20, or 22 digits). New shipments may take 1-4 hours to appear in tracking.

typescript
1const cleanedNumber = trackingNumber.replace(/[\s-]/g, '');

FEDEX_CLIENT_ID is undefined in the Vercel function despite being set in environment variables

Cause: The variable may have been added to Vercel after the current deployment was built, or it's accidentally prefixed with NEXT_PUBLIC_ which behaves differently from non-prefixed variables.

Solution: Verify the exact variable name in Vercel Dashboard → Settings → Environment Variables. Trigger a full redeploy (not just a redeploy with cached build) after adding new variables. Check that no NEXT_PUBLIC_ prefix was accidentally added.

Best practices

  • Cache OAuth2 tokens for their full lifetime (3600 seconds minus a safety buffer) to avoid requesting a new token on every API call
  • Use separate FedEx credentials (FEDEX_SANDBOX=true) for Vercel Preview deployments to avoid accidentally triggering real charges during development
  • Always validate shipping addresses before creating shipments — invalid addresses cause label creation failures that waste both API calls and time
  • Handle FedEx's error response structure carefully — error details appear in the alerts array alongside successful data, so check for errors even when the HTTP status is 200
  • Store generated shipment tracking numbers in your database immediately after label creation in case the response needs to be recovered later
  • Use FedEx's real-time rates rather than hardcoded rate estimates — shipping rates change based on fuel surcharges and destination zones
  • Implement a graceful fallback for when FedEx API is unavailable — display a message asking users to contact support rather than showing a blank rate table

Alternatives

Frequently asked questions

Do I need a FedEx account to use the API, or just developer credentials?

You need both. Developer credentials (client ID and secret) come from registering at developer.fedex.com and creating a project. A FedEx account number (separate from developer credentials) is required for rate quotes and shipment creation — it identifies who is shipping and who is billed. If you don't have a FedEx account, you can create one at fedex.com/open-account.

How long does it take to move from sandbox to production FedEx API?

After completing development in the sandbox, you apply for production access through the FedEx Developer Portal. Approval typically takes 1-3 business days. FedEx reviews your integration and may require you to demonstrate that you're implementing shipping correctly. Once approved, you receive production credentials (different from sandbox credentials) to swap in your environment variables.

Can I generate shipping labels and print them from my V0 app?

Yes. The FedEx Ship API (/ship/v1/shipments) creates shipments and returns a base64-encoded label image (PDF or PNG). Your API route decodes this and returns it as a downloadable file, or as a data URL that renders in a browser for printing. Label generation requires both developer credentials and a valid FedEx account number with active billing.

What happens to cached OAuth tokens when Vercel serverless functions scale?

Module-level token caching in Vercel serverless functions persists within the same function instance's lifetime (warm instance) but is not shared across different instances. When traffic spikes create new function instances, each will request its own token. This is acceptable — FedEx allows many concurrent valid tokens per client credentials. For cross-instance token sharing, store tokens in Upstash Redis using the Vercel Marketplace integration.

Is the FedEx REST API replacing the old SOAP API?

Yes. FedEx launched the REST API in 2022 and announced the SOAP API (FedEx Web Services) will be retired. FedEx has extended the SOAP deprecation timeline multiple times, but new integrations should use the REST API exclusively. This guide uses the current REST API at apis.fedex.com and apis-sandbox.fedex.com.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.