Integrate the UPS REST API with your Lovable app by creating a Supabase Edge Function that authenticates with UPS OAuth2 client credentials and proxies rating, shipping, and tracking requests. Store your UPS Client ID and Client Secret in Cloud → Secrets, then build shipping calculator UIs that call your Edge Function securely. Your customers get real-time UPS rates and tracking without API keys ever reaching the browser.
Build real-time UPS shipping features in your Lovable app
The UPS REST API replaces the older XML-based API suite and covers three core capabilities your e-commerce or logistics app needs: Rating (get live shipping rates for any service level), Shipping (create shipments and generate labels), and Tracking (retrieve package status and delivery estimates). Because UPS uses OAuth2 client credentials for authentication, every API call requires a valid bearer token obtained by exchanging your Client ID and Secret — a flow that must happen server-side.
Lovable's Edge Functions, running on Deno at the edge, are the right place for this token exchange. You store your UPS credentials once in Cloud → Secrets, write an Edge Function that fetches tokens and forwards requests to UPS, and your React frontend calls only your own Edge Function URL. This architecture means your UPS credentials are never in browser memory, never in your Git history, and protected by Lovable's SOC 2 Type II security infrastructure that blocks approximately 1,200 hardcoded keys per day.
Common use cases include shipping calculators that show customers live UPS rates at checkout, order management dashboards that track shipment status automatically, and fulfillment tools that generate UPS labels server-side. This guide walks through each piece — from UPS developer account setup to a complete working shipping rate UI in Lovable.
Integration method
UPS API uses OAuth2 client credentials flow, which requires server-side token management. Your Lovable app calls a Supabase Edge Function that fetches a fresh OAuth2 access token from UPS, then forwards the request to the appropriate UPS endpoint. This keeps your Client ID and Client Secret in Lovable's encrypted Secrets store, never exposed to browser code.
Prerequisites
- A Lovable account with an active project (free tier works for development)
- A UPS developer account at developer.ups.com with a registered application to get Client ID and Client Secret
- Basic familiarity with the Lovable chat interface and Cloud tab
- Your store's origin address and typical package dimensions ready for testing
- A UPS account number if you plan to generate shipping labels (not required for rate queries and tracking)
Step-by-step guide
Create a UPS developer account and register an application
Create a UPS developer account and register an application
Navigate to developer.ups.com and sign in with your UPS.com account, or create a new one if you do not have one. Once logged in, click 'My Apps' in the top navigation, then click the 'Add Apps' button. Give your application a name that reflects your project (for example, 'MyStore Shipping'). Under 'Products,' select the APIs you need: 'Rating' for shipping rate calculations, 'Tracking' for package status, and 'Shipping' if you plan to generate labels. Accept the terms and click 'Save.' UPS will generate a Client ID and a Client Secret for your application — copy both values immediately and store them somewhere temporary like a password manager. You will also see a 'Redirect URI' field; for server-to-server OAuth2 client credentials flow you can enter any placeholder URL such as https://example.com since redirects are not used in this flow. Note that UPS has separate sandbox and production environments — for development, use the sandbox base URL (https://wwwcie.ups.com) and for production use (https://onlinetools.ups.com). Sandbox credentials are separate from production credentials, so you will need to register two applications or toggle environments in your UPS developer portal.
Pro tip: UPS sandbox accounts have test tracking numbers documented in the developer portal under 'Testing Resources.' Use tracking number 1Z999AA10123456784 to test the tracking endpoint without needing a real shipment.
Expected result: You have a UPS Client ID (a long alphanumeric string) and a Client Secret. You know whether you are targeting sandbox or production.
Store UPS credentials in Cloud → Secrets
Store UPS credentials in Cloud → Secrets
In Lovable, click the '+' icon next to the Preview panel to open the Cloud tab. Navigate to the 'Secrets' section. Click 'Add Secret' and create three secrets. First, add UPS_CLIENT_ID and paste your UPS application's Client ID as the value. Second, add UPS_CLIENT_SECRET and paste your Client Secret. Third, add UPS_BASE_URL and set the value to https://wwwcie.ups.com for sandbox testing, or https://onlinetools.ups.com when you are ready for production. Using a separate secret for the base URL makes switching between sandbox and production easy without touching your Edge Function code. Secrets in Lovable are encrypted at rest, never visible in your project's source code, and only accessible from Edge Functions through Deno.env.get(). Lovable's security system actively blocks approximately 1,200 hardcoded API keys per day — storing credentials in Secrets is the only safe approach. Never paste your Client ID or Client Secret in the Lovable chat interface, since on the free tier chat history is visible to others.
Pro tip: Name your secrets exactly as shown — UPS_CLIENT_ID, UPS_CLIENT_SECRET, UPS_BASE_URL — because the Edge Function code in the next step references these exact names. A mismatch causes a P0001 error in your Edge Function logs.
Expected result: Three secrets appear in your Cloud → Secrets panel: UPS_CLIENT_ID, UPS_CLIENT_SECRET, and UPS_BASE_URL.
Create the UPS proxy Edge Function
Create the UPS proxy Edge Function
Open the Lovable chat and describe the Edge Function you need. Lovable will generate the TypeScript code, deploy it to your Cloud project, and make it callable from your frontend. The Edge Function handles UPS OAuth2 token exchange automatically — it fetches a fresh token before each API call using the client credentials grant type, caches it in memory for its 14400-second validity period, and forwards your request with the correct Authorization header. The function accepts a POST request with a JSON body containing the UPS endpoint path and the request payload, then returns the UPS response to your frontend. This proxy pattern eliminates CORS issues entirely, since your React frontend calls your own Supabase domain rather than ups.com directly.
Create a Supabase Edge Function at supabase/functions/ups-proxy/index.ts that acts as a secure proxy to the UPS REST API. The function should: 1) Read UPS_CLIENT_ID, UPS_CLIENT_SECRET, and UPS_BASE_URL from Deno environment variables. 2) Accept POST requests with a JSON body containing 'endpoint' (the UPS API path, e.g. '/api/rating/v2409/rate') and 'payload' (the request body to forward). 3) First make a POST to UPS_BASE_URL + '/security/v1/oauth/token' with Content-Type application/x-www-form-urlencoded and body 'grant_type=client_credentials', using Basic auth with client ID and secret, to get an OAuth2 access token. 4) Then make a POST to UPS_BASE_URL + endpoint with the access token as Bearer token and forward the payload. 5) Return the UPS response as JSON. Include proper CORS headers for browser access.
Paste this in Lovable chat
1import { serve } from "https://deno.land/std@0.168.0/http/server.ts";23const corsHeaders = {4 "Access-Control-Allow-Origin": "*",5 "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",6};78let cachedToken: { value: string; expiresAt: number } | null = null;910async function getAccessToken(11 baseUrl: string,12 clientId: string,13 clientSecret: string14): Promise<string> {15 const now = Date.now();16 if (cachedToken && cachedToken.expiresAt > now + 60000) {17 return cachedToken.value;18 }1920 const credentials = btoa(`${clientId}:${clientSecret}`);21 const response = await fetch(`${baseUrl}/security/v1/oauth/token`, {22 method: "POST",23 headers: {24 "Content-Type": "application/x-www-form-urlencoded",25 Authorization: `Basic ${credentials}`,26 },27 body: "grant_type=client_credentials",28 });2930 if (!response.ok) {31 const error = await response.text();32 throw new Error(`UPS OAuth error: ${response.status} ${error}`);33 }3435 const data = await response.json();36 cachedToken = {37 value: data.access_token,38 expiresAt: now + data.expires_in * 1000,39 };40 return cachedToken.value;41}4243serve(async (req) => {44 if (req.method === "OPTIONS") {45 return new Response("ok", { headers: corsHeaders });46 }4748 try {49 const clientId = Deno.env.get("UPS_CLIENT_ID");50 const clientSecret = Deno.env.get("UPS_CLIENT_SECRET");51 const baseUrl = Deno.env.get("UPS_BASE_URL") ?? "https://wwwcie.ups.com";5253 if (!clientId || !clientSecret) {54 throw new Error("UPS credentials not configured in Secrets");55 }5657 const { endpoint, payload } = await req.json();58 if (!endpoint) throw new Error("'endpoint' is required in request body");5960 const token = await getAccessToken(baseUrl, clientId, clientSecret);6162 const upsResponse = await fetch(`${baseUrl}${endpoint}`, {63 method: "POST",64 headers: {65 "Content-Type": "application/json",66 Authorization: `Bearer ${token}`,67 transId: crypto.randomUUID(),68 transactionSrc: "lovable-app",69 },70 body: JSON.stringify(payload),71 });7273 const data = await upsResponse.json();7475 return new Response(JSON.stringify(data), {76 headers: { ...corsHeaders, "Content-Type": "application/json" },77 status: upsResponse.ok ? 200 : upsResponse.status,78 });79 } catch (error) {80 return new Response(81 JSON.stringify({ error: error.message }),82 { headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 500 }83 );84 }85});Pro tip: The UPS REST API requires two custom headers on every request: transId (a unique ID per request) and transactionSrc (identifies your application). The Edge Function above generates a UUID for transId automatically.
Expected result: The ups-proxy Edge Function appears in your Cloud tab under Edge Functions and shows a green deployed status. You can test it by sending a POST request to its URL.
Build a shipping rate calculator UI
Build a shipping rate calculator UI
With the Edge Function deployed, prompt Lovable to build a React component that collects shipment details from the user and displays live UPS rates. The UPS Rating API accepts a structured JSON payload that specifies the shipper address, ship-to address, and package details, then returns an array of available shipping services with rates. Your component sends this payload to your ups-proxy Edge Function with the endpoint path /api/rating/v2409/rate. Lovable generates the form fields, API call logic, loading state, and results display. You can then customize the appearance using Lovable's Visual Editing mode without consuming AI credits. The component should handle the case where no rates are returned (for example, when an address is invalid or service is not available for that route), showing a friendly error message rather than a blank screen.
Build a ShippingCalculator React component that calls my Supabase Edge Function at /functions/v1/ups-proxy. The form should have fields for: origin zip code, destination zip code, package weight (lbs), package length, width, and height (inches). On submit, POST to /functions/v1/ups-proxy with this body structure: { endpoint: '/api/rating/v2409/rate', payload: { RateRequest: { Request: { RequestOption: 'Shop' }, Shipment: { Shipper: { Address: { PostalCode: originZip, CountryCode: 'US' } }, ShipTo: { Address: { PostalCode: destZip, CountryCode: 'US' } }, ShipFrom: { Address: { PostalCode: originZip, CountryCode: 'US' } }, Package: { PackagingType: { Code: '02' }, Dimensions: { UnitOfMeasurement: { Code: 'IN' }, Length: length, Width: width, Height: height }, PackageWeight: { UnitOfMeasurement: { Code: 'LBS' }, Weight: weight } } } } } }. Display results as cards showing service name, rate, and guaranteed delivery date. Show a loading spinner while fetching.
Paste this in Lovable chat
Pro tip: UPS service codes to display names: '01' = Next Day Air, '02' = 2nd Day Air, '03' = Ground, '12' = 3 Day Select, '13' = Next Day Air Saver. Map these in your component for friendly display names.
Expected result: A shipping calculator form appears in your app preview. Entering zip codes and package dimensions and clicking 'Get Rates' returns a list of UPS service options with prices.
Add package tracking to your app
Add package tracking to your app
The UPS Tracking API lets your customers look up the status of any UPS shipment by tracking number. You call the same ups-proxy Edge Function but with the tracking endpoint and a GET-style payload. The response contains an array of activity events — each with a timestamp, location, and status description — that you can display as a timeline. A common pattern is to store tracking numbers in your Supabase database when an order ships, then surface a tracking status widget on the customer's order detail page that calls the Edge Function automatically when the page loads, so customers see current status without manually entering a tracking number. For a seller dashboard, you can batch-query multiple tracking numbers and show a table of all active shipments with their latest status. Be aware that the UPS Tracking API uses a GET endpoint with a query parameter rather than a POST endpoint — your Edge Function will need a small adjustment to handle GET forwarding for tracking requests, or you can encode the tracking number in the POST payload and have your function handle both cases.
Add a package tracking feature. Create a TrackingWidget component that accepts a trackingNumber prop. On mount, call /functions/v1/ups-proxy with endpoint '/api/track/v1/details/' + trackingNumber (as a GET request forwarded through the proxy). Display the current status prominently, the estimated delivery date, and a vertical timeline of all tracking activities with timestamp, location city/state, and description for each event. If the tracking number is not found, show 'Tracking information not available yet.'
Paste this in Lovable chat
Pro tip: The UPS Tracking API returns delivery status codes. Map them for readability: 'I' = In Transit, 'D' = Delivered, 'X' = Exception, 'P' = Pickup, 'M' = Manifest Pickup. Display the full status text from the response description field.
Expected result: Entering a UPS tracking number displays a timeline of delivery events with the current status and estimated delivery date.
Test end-to-end and switch to production credentials
Test end-to-end and switch to production credentials
With sandbox testing complete, verify all three UPS API flows work correctly before switching to production. For rate queries, test with both domestic and international addresses to confirm rate cards render properly. For tracking, use UPS's documented sandbox tracking numbers (available in the UPS developer portal under Testing Resources) to simulate different delivery statuses — in-transit, delivered, and exception states. Once you are satisfied with the integration, return to your UPS developer portal and create a new Production application under My Apps. Copy the production Client ID and Client Secret, then update your Lovable Cloud → Secrets: change UPS_CLIENT_ID and UPS_CLIENT_SECRET to the production values, and update UPS_BASE_URL from https://wwwcie.ups.com to https://onlinetools.ups.com. No code changes are needed — the Edge Function reads credentials from environment variables at runtime, so the switch takes effect immediately. For complex production configurations including UPS account number integration for negotiated rates or high-volume label generation, RapidDev's team can help configure the full shipment creation workflow with your UPS account settings.
Pro tip: UPS negotiated rates (your account's discounted rates rather than published retail rates) require adding your UPS Account Number to the Rating API request payload under Shipment.ShipperNumber. Without this, you see published retail rates only.
Expected result: Your app correctly displays live UPS rates and tracks packages using production UPS credentials. The UPS_BASE_URL secret points to onlinetools.ups.com.
Common use cases
Shipping rate calculator at checkout
Show customers live UPS shipping options and prices before they complete a purchase. The calculator accepts origin zip, destination zip, package weight, and dimensions, then returns all available UPS service levels with rates and estimated delivery dates.
Build a shipping rate calculator component that accepts origin zip code, destination zip code, package weight in pounds, and package dimensions in inches. Call my /functions/v1/ups-proxy Edge Function with the rating endpoint. Display the results as a list of UPS service options (Ground, 2nd Day Air, Next Day Air) with price and estimated delivery date for each.
Copy this prompt to try it in Lovable
Order tracking dashboard
Give customers a self-service tracking page where they enter a UPS tracking number and see the full delivery history with status milestones. Integrate this into your order confirmation emails by deep-linking to the tracking page.
Create an order tracking page where users can enter a UPS tracking number. Call my /functions/v1/ups-proxy Edge Function with the tracking endpoint. Display the current status, estimated delivery date, and a timeline of all tracking events with timestamps and locations. Show a progress bar indicating how far along the delivery is.
Copy this prompt to try it in Lovable
Automated shipping label generation
After an order is placed and paid, automatically create a UPS shipment and generate a printable label. This is useful for small businesses managing fulfillment from a Lovable-built admin dashboard without needing separate shipping software.
Add a 'Generate UPS Label' button to the order detail page in my admin dashboard. When clicked, call my /functions/v1/ups-proxy Edge Function with the shipper and recipient addresses from the order record. Display the tracking number and show a link to download the PDF label. Save the tracking number back to the order record in Supabase.
Copy this prompt to try it in Lovable
Troubleshooting
Edge Function returns 401 with 'InvalidClientId' or 'Client credentials are invalid'
Cause: The UPS OAuth2 token request is failing because the Client ID and Client Secret do not match, or you are using sandbox credentials against the production endpoint (or vice versa).
Solution: Open Cloud → Secrets and verify UPS_CLIENT_ID and UPS_CLIENT_SECRET match exactly what is shown in your UPS developer portal under My Apps. Also confirm UPS_BASE_URL matches the environment — wwwcie.ups.com for sandbox, onlinetools.ups.com for production. UPS sandbox and production credentials are completely separate and non-interchangeable.
Rating API returns empty RatedShipment array or 'No rates available'
Cause: The package dimensions or weight exceed service limits for the selected route, or the postal codes provided are not valid for the selected country code.
Solution: Verify that the PackageWeight is between 0.1 and 150 lbs for UPS Ground. Ensure CountryCode matches the postal code format — US zip codes require 'US', Canadian postal codes require 'CA'. In sandbox mode, use real US zip codes (e.g., 10001 for New York, 90210 for Beverly Hills) rather than made-up numbers. Check that RequestOption is set to 'Shop' to return all available services, not a specific service code.
Tracking API returns 'Tracking number not found' for a valid tracking number
Cause: The UPS Tracking API v1 endpoint requires the tracking number as a URL path parameter, not a request body field. If your Edge Function is constructing the URL incorrectly, UPS returns a not-found error.
Solution: Verify your Edge Function constructs the tracking URL as GET /api/track/v1/details/{trackingNumber} with the tracking number in the path. The tracking API also requires the Accept header set to application/json and may need a Locale header (en_US) for response localization. Check Cloud → Logs for the exact URL being called.
1// Correct tracking URL construction in Edge Function2const trackingNumber = payload.trackingNumber;3const upsResponse = await fetch(4 `${baseUrl}/api/track/v1/details/${trackingNumber}`,5 {6 method: "GET",7 headers: {8 Authorization: `Bearer ${token}`,9 transId: crypto.randomUUID(),10 transactionSrc: "lovable-app",11 Accept: "application/json",12 Locale: "en_US",13 },14 }15);CORS error in browser console when calling the Edge Function
Cause: The Edge Function is not returning CORS headers, or the OPTIONS preflight request is not being handled before the main logic runs.
Solution: Ensure your Edge Function returns the CORS headers on every response, including error responses, and returns a 200 OK response to OPTIONS preflight requests before any other logic. The corsHeaders object must be spread into every Response constructor in the function, including catch blocks. Review the code in the Edge Function step above for the correct pattern.
1// Always handle OPTIONS before other logic2if (req.method === "OPTIONS") {3 return new Response("ok", { headers: corsHeaders });4}Best practices
- Store UPS_BASE_URL as a secret rather than hardcoding it, so switching between sandbox and production requires no code changes — only a secret value update.
- Cache the OAuth2 access token in Edge Function memory for its full validity period (14,400 seconds) to avoid an unnecessary token request on every shipping rate query, which adds latency and burns rate limit quota.
- Always validate user-entered zip codes and package weights on the frontend before calling the Edge Function, to prevent malformed requests from consuming your UPS API quota unnecessarily.
- Display UPS service names using a human-readable mapping (Ground, 2nd Day Air, etc.) rather than raw service codes, since customers are unfamiliar with codes like '02' or '03'.
- For label generation in production, store the generated tracking number in your Supabase orders table immediately after creating the UPS shipment, so you can display tracking status to customers on their order page.
- Use UPS's sandbox environment during all development and staging testing — sandbox traffic does not count against your production rate limits and does not generate real shipments.
- Add rate limiting to your Edge Function using Supabase's built-in rate limiting or a simple counter in your database to prevent customers from hammering the UPS Rating API with repeated requests.
- Review UPS API version numbers periodically — UPS uses date-based versioning (e.g., v2409 for September 2024) and deprecates older versions on a published schedule available in the developer portal.
Alternatives
ShipStation aggregates UPS alongside 40+ other carriers and adds multi-channel order management, making it a better fit if you need to compare rates across carriers or manage orders from multiple sales channels.
Printful handles all shipping logistics automatically for print-on-demand products, so you never interact with carrier APIs directly — the right choice if you sell custom merchandise rather than shipping your own inventory.
Shopify's native shipping includes UPS rate display and label generation built into the checkout flow, making it better for full e-commerce stores that do not need a custom-built shipping calculator.
Frequently asked questions
Do I need a UPS account number to use the UPS API?
You do not need a UPS account number to query published shipping rates or to use the Tracking API. A UPS developer account with a registered application is sufficient for those endpoints. However, to access your negotiated (discounted) rates rather than retail rates, and to generate shipping labels billed to your UPS account, you do need a UPS account number. You can open a UPS account at ups.com/account for free.
Can I use the UPS API on Lovable's free tier?
Yes. The UPS API integration works on Lovable's free tier because Edge Functions are included in all plans. The free tier limits you to 5 daily AI credits for building, but the Edge Function itself runs without credit consumption once deployed. UPS provides free developer access to their sandbox environment, so you can build and test without any UPS charges during development.
Why do I need an Edge Function instead of calling UPS directly from my React frontend?
The UPS API uses OAuth2 client credentials flow, which requires your Client ID and Client Secret to be sent to UPS servers to obtain an access token. If you did this from the browser, your credentials would be visible in browser developer tools and network logs. Edge Functions run server-side where credentials stay encrypted, and they also resolve the CORS issue since UPS API endpoints do not include CORS headers for browser requests.
How do I display UPS rates in a multi-currency checkout?
The UPS Rating API returns rates in the currency associated with your shipper address — typically USD for US-based shippers. To display rates in other currencies, you would need a separate currency conversion API. For most use cases, displaying rates in USD and noting 'rates in USD' is the simplest approach. If you require dynamic currency conversion, add a second Edge Function that calls an exchange rate API and converts the UPS rate before returning it to the frontend.
What happens if the UPS API is down or slow?
Add a timeout to your Edge Function's UPS fetch call (for example, 10 seconds) and return a graceful error message when it triggers. On the frontend, display a message like 'Live rates unavailable — please try again or contact us for a shipping quote.' For checkout flows where shipping rates are required, you can store fallback flat rates in your Supabase database and display those when the UPS API is unreachable.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation