Connect Bolt.new to the UPS REST API for package tracking, rate quotes, and label creation by building a Next.js API route that handles OAuth 2.0 client credentials authentication. UPS migrated to OAuth 2.0 in 2023 — use the new REST API, not the deprecated XML API. Store your Client ID and Client Secret in environment variables. Outbound API calls work in Bolt's WebContainer; deploy to Netlify for production shipping dashboards.
Integrate UPS Shipping APIs into Your Bolt.new App
UPS delivers 24 million packages daily and offers a comprehensive REST API for developers building shipping integrations. The UPS API suite covers the full shipping lifecycle: real-time rate quotes, label generation, package tracking, address validation, time-in-transit estimates, and returns management. Whether you are building an e-commerce platform, a logistics dashboard, or a customer-facing package tracker, the UPS API provides the data you need.
In 2023, UPS significantly updated its developer platform by migrating from the legacy XML-based API to a modern OAuth 2.0 REST API. If you find older tutorials using SOAP or XML endpoints, those are deprecated and the UPS documentation now points to the REST API as the standard approach. The new API uses industry-standard Bearer token authentication, making it straightforward to integrate with Bolt.new's Next.js API routes.
The integration works by obtaining an access token from UPS's OAuth endpoint using your Client ID and Client Secret (obtained from the UPS Developer Portal after free registration). This token is then included as a Bearer token in all subsequent UPS API calls. Since tokens expire after a few hours, your API route should handle token refresh or re-fetch the token on each request — for low-traffic apps, re-fetching on each call is simple and reliable. All UPS API communication happens server-side in your API routes, keeping credentials secure and avoiding CORS issues.
Integration method
The UPS REST API uses OAuth 2.0 client credentials flow for authentication. In Bolt.new, a Next.js API route exchanges your Client ID and Secret for an access token, then uses that token to call UPS tracking, rating, and shipping endpoints. The OAuth token is fetched server-side and never exposed to the client. Outbound calls to UPS work in Bolt's WebContainer during development.
Prerequisites
- A UPS.com account (free personal or business account)
- A UPS Developer Kit registration at https://developer.ups.com — click 'Get Started' and register your app to receive a Client ID and Client Secret
- Your UPS account number (found in My UPS on ups.com under 'View My Accounts') — required for some API operations like label creation
- A Bolt.new account with a Next.js project open
- A Netlify account for deploying the production shipping dashboard
Step-by-step guide
Register for the UPS Developer Kit and Get OAuth Credentials
Register for the UPS Developer Kit and Get OAuth Credentials
The UPS Developer Portal has changed significantly with the 2023 migration to REST APIs. To get started, visit https://developer.ups.com and click 'Log In with UPS.com' using your existing UPS account, or create a new account. Once logged in, navigate to 'My Apps' and click 'Add Apps'. Give your app a name and description, select the APIs you need (at minimum: Tracking API, Rating API), and click 'Add'. UPS will generate a Client ID and Client Secret for your application. Copy both values immediately and store them securely. The Client ID is a public identifier; the Client Secret must be kept private and only used server-side. The UPS Developer Portal has two environments: CIE (Customer Integration Environment, for testing) and Production. During development, use the CIE environment, which uses https://wwwcie.ups.com as the base URL. For production, switch to https://onlinetools.ups.com. You do not need to request production access separately for basic tracking and rating operations — the same credentials work in both environments at different base URLs. UPS also provides test tracking numbers you can use during development without creating real shipments: 1Z999AA10123456784 is a commonly used test tracking number in UPS documentation.
1# .env — add to project root2UPS_CLIENT_ID=your_ups_client_id_here3UPS_CLIENT_SECRET=your_ups_client_secret_here4UPS_ACCOUNT_NUMBER=your_ups_account_number_here5# Use CIE for testing, onlinetools.ups.com for production6UPS_BASE_URL=https://wwwcie.ups.comPro tip: Save your Client Secret in a password manager immediately after generation — UPS only shows it once. If you lose it, you must generate a new one in the Developer Portal under My Apps → your app → Credentials.
Expected result: You have a UPS Client ID, Client Secret, and your account number recorded. The .env file is created in your Bolt project.
Implement OAuth 2.0 Token Authentication
Implement OAuth 2.0 Token Authentication
UPS uses the OAuth 2.0 client credentials flow, which means you exchange your Client ID and Client Secret for a short-lived access token, then use that token as a Bearer token in all UPS API requests. The token endpoint is POST https://wwwcie.ups.com/security/v1/oauth/token (or https://onlinetools.ups.com/security/v1/oauth/token for production). The request uses HTTP Basic authentication with the Client ID as the username and Client Secret as the password, encoded as Base64. The grant_type parameter must be 'client_credentials' and the Content-Type must be 'application/x-www-form-urlencoded'. The response includes an access_token and a token_type of 'Bearer'. UPS tokens are valid for 14,400 seconds (4 hours). For a production application, you would cache the token and only re-fetch when it expires. For simplicity in Bolt development, fetching a new token on each API call is acceptable — UPS rate limits are generous enough that this does not cause problems in typical usage. Build a utility function that handles the token fetch so all your UPS API routes can reuse it.
Create a UPS API authentication utility in this Next.js project. Create a file at lib/ups-auth.ts that exports an async function getUpsToken() which calls the UPS OAuth endpoint at process.env.UPS_BASE_URL/security/v1/oauth/token using HTTP Basic auth with process.env.UPS_CLIENT_ID and process.env.UPS_CLIENT_SECRET. The function should use grant_type=client_credentials with Content-Type: application/x-www-form-urlencoded. Return the access_token string. Add TypeScript types for the OAuth response.
Paste this in Bolt.new chat
1// lib/ups-auth.ts2interface UpsTokenResponse {3 token_type: string;4 issued_at: string;5 client_id: string;6 access_token: string;7 expires_in: string;8 status: string;9}1011export async function getUpsToken(): Promise<string> {12 const clientId = process.env.UPS_CLIENT_ID;13 const clientSecret = process.env.UPS_CLIENT_SECRET;14 const baseUrl = process.env.UPS_BASE_URL || 'https://wwwcie.ups.com';1516 if (!clientId || !clientSecret) {17 throw new Error('UPS_CLIENT_ID and UPS_CLIENT_SECRET must be configured');18 }1920 // UPS OAuth requires Basic auth with Base64-encoded credentials21 const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');2223 const response = await fetch(`${baseUrl}/security/v1/oauth/token`, {24 method: 'POST',25 headers: {26 'Authorization': `Basic ${credentials}`,27 'Content-Type': 'application/x-www-form-urlencoded',28 'x-merchant-id': process.env.UPS_ACCOUNT_NUMBER || '',29 },30 body: 'grant_type=client_credentials',31 });3233 if (!response.ok) {34 const error = await response.text();35 throw new Error(`UPS OAuth error ${response.status}: ${error}`);36 }3738 const data: UpsTokenResponse = await response.json();39 return data.access_token;40}Pro tip: For production apps with high request volume, cache the access token in a module-level variable and only refresh when it is within 5 minutes of expiry. The token's issued_at and expires_in fields let you calculate the expiry time.
Expected result: The getUpsToken utility function is created. Calling it from a test API route returns a valid UPS access token string.
Build the UPS Tracking API Route
Build the UPS Tracking API Route
With token authentication working, build the tracking API route that fetches package status from UPS. The UPS Tracking API endpoint is GET /api/track/v1/details/{trackingNumber} and requires a Bearer token, a transId (a unique string you generate per request, like a UUID), a transactionSrc (your app name), and optionally the locale and returnSignature parameters. The response contains a shipmentResponse.shipment array — UPS can return multiple packages for a single tracking number in the case of multi-piece shipments. Each shipment contains the origin/destination addresses, current status, estimated delivery date, and an array of activity objects representing each scan event with timestamp, description, and location. The activity array is ordered with the most recent event first. Map these into a clean timeline structure for your frontend. The UPS tracking response also includes a statusDescription at the top level for quick status display: values include 'In Transit', 'Out for Delivery', 'Delivered', 'Exception', and 'Pickup'. The test tracking number 1Z999AA10123456784 returns a sample response in the CIE environment.
Create a UPS tracking API route at app/api/ups/track/route.ts. Import getUpsToken from lib/ups-auth.ts. The route accepts a GET request with a trackingNumber query parameter. Call the UPS Tracking API at process.env.UPS_BASE_URL/api/track/v1/details/{trackingNumber} with the Bearer token and required headers (transId using crypto.randomUUID(), transactionSrc='BoltShippingApp'). Parse the response and return: trackingNumber, status, statusDescription, estimatedDelivery, origin (city/state), destination (city/state), and an activities array with date, time, location, and description for each scan event.
Paste this in Bolt.new chat
1// app/api/ups/track/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { getUpsToken } from '@/lib/ups-auth';45interface UpsActivity {6 location?: { address?: { city?: string; stateProvince?: string; countryCode?: string } };7 status?: { description?: string; type?: string };8 date?: string;9 time?: string;10}1112export async function GET(request: NextRequest) {13 const trackingNumber = new URL(request.url).searchParams.get('trackingNumber');14 if (!trackingNumber) {15 return NextResponse.json({ error: 'trackingNumber is required' }, { status: 400 });16 }1718 const baseUrl = process.env.UPS_BASE_URL || 'https://wwwcie.ups.com';1920 try {21 const token = await getUpsToken();2223 const response = await fetch(24 `${baseUrl}/api/track/v1/details/${encodeURIComponent(trackingNumber)}`,25 {26 headers: {27 Authorization: `Bearer ${token}`,28 transId: crypto.randomUUID(),29 transactionSrc: 'BoltShippingApp',30 'Content-Type': 'application/json',31 },32 }33 );3435 if (!response.ok) {36 const error = await response.json();37 return NextResponse.json(38 { error: error.response?.errors?.[0]?.message || 'UPS API error' },39 { status: response.status }40 );41 }4243 const data = await response.json();44 const shipment = data.trackResponse?.shipment?.[0];4546 if (!shipment) {47 return NextResponse.json({ error: 'Tracking number not found' }, { status: 404 });48 }4950 const package_ = shipment.package?.[0];51 const activities: UpsActivity[] = package_?.activity || [];5253 return NextResponse.json({54 trackingNumber,55 status: package_?.currentStatus?.description || 'Unknown',56 estimatedDelivery: package_?.deliveryDate?.[0]?.date || null,57 origin: {58 city: shipment.shipper?.address?.city,59 state: shipment.shipper?.address?.stateProvince,60 },61 destination: {62 city: shipment.shipTo?.address?.city,63 state: shipment.shipTo?.address?.stateProvince,64 },65 activities: activities.map((a) => ({66 date: a.date,67 time: a.time,68 description: a.status?.description,69 location: [70 a.location?.address?.city,71 a.location?.address?.stateProvince,72 a.location?.address?.countryCode,73 ]74 .filter(Boolean)75 .join(', '),76 })),77 });78 } catch (error) {79 const message = error instanceof Error ? error.message : 'Unknown error';80 return NextResponse.json({ error: message }, { status: 500 });81 }82}Pro tip: UPS date and time fields are formatted as YYYYMMDD and HHMMSS respectively. Convert them with: new Date(`${date.slice(0,4)}-${date.slice(4,6)}-${date.slice(6,8)}T${time.slice(0,2)}:${time.slice(2,4)}`)
Expected result: The tracking API route returns structured package tracking data. Testing with the UPS test tracking number returns activity events, status, and estimated delivery information.
Build Rate Quotes and Deploy to Netlify
Build Rate Quotes and Deploy to Netlify
The UPS Rating API provides rate quotes for multiple service levels — Ground, 2-Day, Next Day Air, and international services — in a single request using the 'Shop' endpoint. The payload requires the shipper's postal code, recipient's postal code, package weight, and optionally dimensions. The response includes rates for every available UPS service between those two locations. This is useful for showing customers shipping options at checkout or for internal tools that compare carrier costs. After building the rate and tracking features, deploy your Bolt project to Netlify. Connect via Settings → Applications → Netlify → Connect, then add UPS_CLIENT_ID, UPS_CLIENT_SECRET, UPS_ACCOUNT_NUMBER, and UPS_BASE_URL to Netlify's environment variables under Site Configuration → Environment Variables. Change UPS_BASE_URL to https://onlinetools.ups.com for the production environment. An important WebContainer note: the UPS API does not send webhooks to your application by default — UPS tracking is a pull-based API where you query for status. However, if you build a shipment creation feature and need to receive UPS Quantum View notifications (a push-based tracking subscription service), those HTTP notifications require a deployed URL and cannot reach the Bolt WebContainer. For standard tracking and rating features, the WebContainer preview works perfectly.
Add a UPS rate quote API route at app/api/ups/rates/route.ts. Import getUpsToken from lib/ups-auth.ts. Accept a POST with body containing shipperPostalCode, recipientPostalCode, recipientCountryCode, weightLbs, and optional dimensions (length, width, height in inches). Call the UPS Rating API at process.env.UPS_BASE_URL/api/rating/v1/Shop using the Bearer token. Pass the shipper address with process.env.UPS_ACCOUNT_NUMBER and the package details. Parse the RatedShipment array and return each service's name, total price (in USD), and days in transit. Create a rate calculator page at app/shipping/page.tsx with a form for the input fields and a results table showing the available UPS service options sorted by price.
Paste this in Bolt.new chat
1// app/api/ups/rates/route.ts2import { NextResponse } from 'next/server';3import { getUpsToken } from '@/lib/ups-auth';45export async function POST(request: Request) {6 const { shipperPostalCode, recipientPostalCode, recipientCountryCode, weightLbs } =7 await request.json();89 const baseUrl = process.env.UPS_BASE_URL || 'https://wwwcie.ups.com';10 const accountNumber = process.env.UPS_ACCOUNT_NUMBER;1112 try {13 const token = await getUpsToken();1415 const ratePayload = {16 RateRequest: {17 Request: { SubVersion: '1703', TransactionReference: { CustomerContext: 'RateRequest' } },18 Shipment: {19 Shipper: {20 ShipperNumber: accountNumber,21 Address: { PostalCode: shipperPostalCode, CountryCode: 'US' },22 },23 ShipTo: {24 Address: {25 PostalCode: recipientPostalCode,26 CountryCode: recipientCountryCode || 'US',27 ResidentialAddressIndicator: '',28 },29 },30 ShipFrom: {31 Address: { PostalCode: shipperPostalCode, CountryCode: 'US' },32 },33 Package: {34 PackagingType: { Code: '02', Description: 'Package' },35 PackageWeight: {36 UnitOfMeasurement: { Code: 'LBS' },37 Weight: String(weightLbs || 1),38 },39 },40 },41 },42 };4344 const response = await fetch(`${baseUrl}/api/rating/v1/Shop`, {45 method: 'POST',46 headers: {47 Authorization: `Bearer ${token}`,48 transId: crypto.randomUUID(),49 transactionSrc: 'BoltShippingApp',50 'Content-Type': 'application/json',51 },52 body: JSON.stringify(ratePayload),53 });5455 const data = await response.json();56 const ratedShipments = data.RateResponse?.RatedShipment || [];5758 const rates = ratedShipments.map((s: Record<string, { Code?: string; MonetaryValue?: string; BusinessDaysInTransit?: string }>) => ({59 serviceCode: s.Service?.Code,60 price: parseFloat(s.TotalCharges?.MonetaryValue || '0'),61 currency: 'USD',62 daysInTransit: s.GuaranteedDelivery?.BusinessDaysInTransit || null,63 }));6465 rates.sort((a: { price: number }, b: { price: number }) => a.price - b.price);66 return NextResponse.json({ rates });67 } catch (error) {68 const message = error instanceof Error ? error.message : 'Unknown error';69 return NextResponse.json({ error: message }, { status: 500 });70 }71}Pro tip: UPS service codes map to service names: '03' is UPS Ground, '02' is UPS 2-Day Air, '01' is UPS Next Day Air, '12' is UPS 3 Day Select. Build a lookup object mapping these codes to human-readable names.
Expected result: The rate API returns a sorted list of UPS service options with prices. The rate calculator page shows available UPS services between two zip codes. The app is deployed to Netlify with production environment variables.
Common use cases
Customer-Facing Package Tracker
Build a branded package tracking page where customers enter a UPS tracking number and see real-time status updates, estimated delivery date, and a timeline of scan events. Replaces the need to send customers to UPS.com to check their order status.
Build a UPS package tracking page in Next.js. Create an API route at app/api/ups/track/route.ts that accepts a trackingNumber query param, authenticates with UPS OAuth using process.env.UPS_CLIENT_ID and process.env.UPS_CLIENT_SECRET, then calls the UPS Tracking API at https://onlinetools.ups.com/api/track/v1/details/{trackingNumber}. Return the shipment status, estimated delivery date, and array of activity events with timestamps. Create a tracking page at app/track/page.tsx with a tracking number input and a timeline visualization of scan events.
Copy this prompt to try it in Bolt.new
Shipping Rate Calculator
Add a real-time shipping rate calculator to an e-commerce cart that shows UPS Ground, UPS 2-Day, and UPS Next Day Air rates based on the customer's address and package dimensions. Lets customers choose their preferred speed and cost tradeoff at checkout.
Create a shipping rate calculator in Next.js. Build an API route at app/api/ups/rates/route.ts that accepts a POST with shipper zip, recipient zip, package weight in lbs, and dimensions. Authenticate with UPS OAuth and call the UPS Rating API at https://onlinetools.ups.com/api/rating/v1/Shop to get rates for all available services. Return service name, days in transit, and price for each option. Display the rates in a sortable table with the cheapest and fastest options highlighted.
Copy this prompt to try it in Bolt.new
Multi-Carrier Shipping Dashboard
Build an operations dashboard that displays shipments across multiple carriers — UPS, FedEx, and DHL — in a unified interface. Operations teams can see all outgoing packages, their current status, and expected delivery dates without switching between carrier portals.
Build a multi-carrier shipping dashboard in Next.js. Create a unified shipment list page that fetches tracking data from multiple carrier APIs. Build a UPS tracking API route at app/api/ups/track/route.ts and a FedEx tracking route at app/api/fedex/track/route.ts. Create a Supabase table called 'shipments' with columns: id, carrier, tracking_number, recipient_name, status, estimated_delivery. Build a dashboard that reads from the database and fetches live status for each shipment from the appropriate carrier API.
Copy this prompt to try it in Bolt.new
Troubleshooting
UPS OAuth returns 'Invalid Client' or 401 error
Cause: The UPS_CLIENT_ID or UPS_CLIENT_SECRET is incorrect, or the credentials are not being passed correctly as HTTP Basic auth.
Solution: In your UPS Developer Portal at developer.ups.com, go to My Apps → select your app → Credentials tab and verify your Client ID. If needed, regenerate the Client Secret. Ensure the credentials are passed as Base64-encoded Basic auth — the format is Buffer.from('clientId:clientSecret').toString('base64').
1// Correct Basic auth encoding for UPS OAuth2const credentials = Buffer.from(3 `${process.env.UPS_CLIENT_ID}:${process.env.UPS_CLIENT_SECRET}`4).toString('base64');56headers: { 'Authorization': `Basic ${credentials}` }Tracking API returns 'No data found' for a tracking number
Cause: The tracking number is not in UPS's test system, the CIE environment does not have data for that number, or the number format is incorrect.
Solution: Use the UPS test tracking number 1Z999AA10123456784 which is documented to return sample data in the CIE environment. Real UPS tracking numbers start with '1Z' followed by 16 characters. Verify you are using the CIE base URL (https://wwwcie.ups.com) for testing and onlinetools.ups.com for production tracking numbers.
CORS error when calling UPS API from a React component
Cause: UPS API endpoints do not allow cross-origin requests from browser-based JavaScript. Direct client-side fetch calls to UPS will fail.
Solution: All UPS API calls must go through your Next.js API routes (server-side), never directly from client components. The API route runs server-side and is not subject to browser CORS restrictions. Import the getUpsToken utility only in API route files, not in React components.
1// Wrong — calling UPS API directly from a React component2const token = await getUpsToken(); // This fails in the browser34// Correct — call your own API route from the client5const res = await fetch(`/api/ups/track?trackingNumber=${trackingNumber}`);Rate API returns an empty RatedShipment array
Cause: No UPS services are available between the specified postal codes, the shipper account number is incorrect, or the package weight is zero or missing.
Solution: Ensure weightLbs is a positive number greater than 0. Verify UPS_ACCOUNT_NUMBER is your correct UPS account number (found in My UPS → Account Summary). Try a US domestic route first (e.g., 10001 NY to 90210 CA) to confirm the basic integration works before testing international routes.
Best practices
- Never expose UPS_CLIENT_SECRET as a NEXT_PUBLIC_ variable — it must only be used in server-side API routes
- Cache UPS OAuth tokens for their full 4-hour lifespan to reduce authentication overhead in high-traffic applications
- Always use the CIE environment (wwwcie.ups.com) with test tracking numbers during development before switching to production credentials
- Include error handling for UPS API downtime — UPS has scheduled maintenance windows and occasional outages, so display graceful fallback states
- Store the UPS service code lookup table in a constants file so service names like 'UPS Ground' are consistently displayed throughout your app
- Log UPS API response errors with the full response body during development — UPS error responses often contain detailed messages about what is missing or invalid
- For label generation features, test the full flow in the CIE environment first — generating real labels creates real shipments and incurs charges
Alternatives
FedEx offers a similar REST API for tracking and rating with comparable coverage in North America — use FedEx if your customers or shipping partners primarily use FedEx services.
DHL is the preferred choice for international shipments and European markets where DHL has stronger coverage and more competitive rates than UPS.
AfterShip aggregates tracking data across 900+ carriers including UPS in a single API, making it the better choice when you need multi-carrier tracking without building separate integrations.
ShipStation provides a unified API for multi-carrier shipping including UPS, with order management, batch label printing, and returns — better for e-commerce operations than a direct UPS API integration.
Frequently asked questions
Does Bolt.new work with the UPS API?
Yes. The UPS REST API works well with Bolt.new's Next.js API routes. Your API route handles the OAuth 2.0 authentication flow, fetches tracking or rate data from UPS, and returns it to your React components. Outbound calls to UPS work in Bolt's WebContainer preview, so you can develop and test the integration without deploying.
Is the UPS API free to use?
Registering for the UPS Developer Kit and using the Tracking and Rating APIs is free. You pay only for actual shipments you create using the Shipping API — the cost is the standard UPS shipping rates, not a developer fee. The CIE test environment is completely free for development and testing.
What is the difference between the UPS CIE environment and production?
The CIE (Customer Integration Environment) at wwwcie.ups.com is a sandbox for development and testing with sample data and test tracking numbers. Production at onlinetools.ups.com handles real shipments and returns live tracking data. The same Client ID and Secret work in both environments — you switch by changing the base URL in your environment variables.
Can I generate UPS shipping labels in Bolt.new?
Yes. The UPS Shipping API (Shipment endpoint) creates labels and returns a Base64-encoded PDF or GIF image. Build a server-side API route that calls the Shipment endpoint with your account number, shipper and recipient addresses, and package details. Return the label image data to your frontend for download or display. Always test label creation in the CIE environment first — production label requests create real shipments.
How do I track multiple packages at once in a Bolt.new dashboard?
The UPS Tracking API supports batch tracking requests in a single call by including multiple tracking numbers. Alternatively, store tracking numbers in a Supabase database and make parallel fetch calls to your tracking API route using Promise.all(). Display all results in a table sorted by estimated delivery date. Refresh tracking data on a timer or on user request since UPS tracking events typically update every few hours.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation