To use Shippo with V0, generate a shipping rate comparison UI in V0, then create a Next.js API route at app/api/shippo/route.ts that calls the Shippo REST API using your Shippo API token stored in Vercel environment variables. Shippo returns real-time rates from USPS, UPS, FedEx, and DHL — your API route fetches these and returns them to the V0-generated frontend for display.
Adding Multi-Carrier Shipping to Your V0 App with Shippo
Shippo's biggest advantage for founders building with V0 is its single-API approach to multi-carrier shipping. Instead of integrating separately with USPS, UPS, FedEx, and DHL — each with their own account requirements, API quirks, and pricing structures — Shippo acts as a unified layer that lets you compare rates across all carriers with one API call. For e-commerce apps, order management tools, and fulfillment dashboards built with V0, this dramatically reduces the shipping integration work.
The integration architecture follows V0's standard pattern for external APIs. V0 generates the React frontend — the address input forms, carrier rate comparison tables, label download buttons, and tracking status displays. A Next.js API route handles all the Shippo API calls server-side, using your Shippo API token stored in Vercel environment variables. This keeps your token secure and gives you full control over the request/response cycle, including error handling and rate limiting.
Shippo differs from ShipStation in a critical way: Shippo is developer API-first, designed for programmatic integration into custom apps, while ShipStation is a UI-first fulfillment platform meant for manual order management. If you are building a custom app with V0 and want to embed shipping directly into your workflow, Shippo is the right choice. ShipStation makes more sense if your team needs a standalone fulfillment dashboard without custom development.
Integration method
V0 generates the shipping UI — rate comparison tables, label download buttons, tracking status displays — while a Next.js API route handles all communication with Shippo's REST API server-side, keeping your API token out of the browser. The API route calls Shippo to fetch rates, purchase labels, and retrieve tracking data, then returns the results to the V0-generated React components.
Prerequisites
- A V0 account with a Next.js project generated at v0.dev
- A Shippo account at goshippo.com (free test account available, no credit card required for testing)
- Your Shippo API token from goshippo.com → API → API Keys (test tokens start with shippo_test_)
- A Vercel account with your V0 project deployed via GitHub
- Basic understanding of parcel dimensions (weight, length, width, height) for rate requests
Step-by-step guide
Generate Your Shipping UI in V0
Generate Your Shipping UI in V0
Start by prompting V0 to generate the frontend components for your shipping workflow. Depending on your use case, this could be a rate comparison table for checkout, a label generation form for internal fulfillment, or a tracking status display for customer-facing pages. V0 excels at creating structured data displays like rate comparison tables, and it can wire up the fetch calls to your API routes with the right request format. When writing your V0 prompt, specify the exact API endpoint paths the components should call. This ensures V0 generates the correct fetch() calls without you having to edit the component code manually afterward. For a rate comparison component, the key input fields are: sender address (city, state, ZIP, country), recipient address, and parcel details (weight in ounces, and dimensions in inches). These map directly to Shippo's API request format. After V0 generates the component, check that it handles both the loading state and the error state. Shipping rate API calls typically take 2-4 seconds because Shippo queries multiple carriers in parallel, so a loading skeleton or spinner is important for user experience. Also verify the component passes all required address and parcel fields in the POST body — missing fields are the most common cause of Shippo API errors in V0-generated code. One V0-specific consideration: V0 may generate the rate display with hardcoded mock data initially. If so, ask V0 to replace the mock data with a real fetch call to your API route. V0 sometimes defaults to static data when building UI components before the API layer exists.
Create a shipping rate calculator component. Include a form with: sender ZIP code, recipient ZIP code, package weight (in ounces), and package dimensions (length, width, height in inches). Add a 'Get Rates' button that POSTs to /api/shippo/rates with those values. Display results as a table with columns: Carrier, Service, Estimated Delivery, Price. Show a loading skeleton while the request is in progress and an error message if the fetch fails.
Paste this in V0 chat
Pro tip: Ask V0 to pre-populate the sender address fields with your business address using default values in the React state. This saves users from re-entering the origin address on every rate check.
Expected result: V0 generates a shipping rate calculator component with address and parcel input fields, a fetch call to /api/shippo/rates, and a results table with loading and error states.
Create the Shippo Rates API Route
Create the Shippo Rates API Route
Create the server-side API route that fetches shipping rates from Shippo. In your V0 project, create the file app/api/shippo/route.ts. This route accepts a POST request with the shipment details, calls the Shippo REST API to get rates, and returns the carrier options to the frontend. Shippo's API uses a simple REST structure. You first create a Shipment object (sender address, recipient address, parcel) and Shippo returns available rates from all configured carriers. The API uses your Shippo API token in an Authorization header with the format: Shippo <your_token>. Note the capital S and the space — this is different from the more common Bearer token format. The key decision in this route is whether to use Shippo's npm package (shippo) or call the REST API directly with fetch(). The npm package adds a dependency but provides TypeScript types and simpler method calls. For straightforward rate fetching, calling the REST API directly with fetch() keeps your bundle lean and avoids package version conflicts. The route shown below uses the direct REST approach. Important: Shippo rate requests are asynchronous. When you create a Shipment, you get back an object with a rates array. If the rates array is empty, it may mean the carriers have not finished responding yet — Shippo recommends checking the status field. For real-time use cases, you can use Shippo's synchronous mode by adding async: false to the request, which waits for all carriers to respond before returning. This increases latency but simplifies your code significantly. For most V0 apps, the synchronous approach is recommended.
Add a Next.js API route at app/api/shippo/route.ts that accepts POST requests with fromZip, toZip, weightOz, lengthIn, widthIn, heightIn fields. Call the Shippo REST API at https://api.goshippo.com/shipments/ with Authorization header 'Shippo ' + process.env.SHIPPO_API_TOKEN, set async to false to get synchronous rates, and return the rates array as JSON.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23interface ShippoRate {4 object_id: string;5 provider: string;6 servicelevel: { name: string; token: string };7 amount: string;8 currency: string;9 estimated_days: number;10 duration_terms: string;11}1213interface ShippoShipmentResponse {14 rates: ShippoRate[];15 status: string;16 messages: Array<{ code: string; text: string }>;17}1819export async function POST(request: NextRequest) {20 try {21 const { fromZip, toZip, weightOz, lengthIn, widthIn, heightIn } =22 await request.json();2324 if (!fromZip || !toZip || !weightOz) {25 return NextResponse.json(26 { error: 'fromZip, toZip, and weightOz are required' },27 { status: 400 }28 );29 }3031 const shippoResponse = await fetch('https://api.goshippo.com/shipments/', {32 method: 'POST',33 headers: {34 Authorization: `Shippo ${process.env.SHIPPO_API_TOKEN}`,35 'Content-Type': 'application/json',36 },37 body: JSON.stringify({38 address_from: {39 zip: fromZip,40 country: 'US',41 },42 address_to: {43 zip: toZip,44 country: 'US',45 },46 parcels: [47 {48 length: String(lengthIn || 10),49 width: String(widthIn || 8),50 height: String(heightIn || 4),51 distance_unit: 'in',52 weight: String(weightOz),53 mass_unit: 'oz',54 },55 ],56 async: false,57 }),58 });5960 if (!shippoResponse.ok) {61 const errorData = await shippoResponse.text();62 console.error('Shippo API error:', errorData);63 return NextResponse.json(64 { error: 'Failed to fetch shipping rates' },65 { status: shippoResponse.status }66 );67 }6869 const data: ShippoShipmentResponse = await shippoResponse.json();7071 // Sort rates by price ascending72 const sortedRates = data.rates73 .filter((rate) => rate.amount)74 .sort((a, b) => parseFloat(a.amount) - parseFloat(b.amount));7576 return NextResponse.json({ rates: sortedRates });77 } catch (error) {78 console.error('Shippo route error:', error);79 return NextResponse.json(80 { error: 'Internal server error' },81 { status: 500 }82 );83 }84}Pro tip: Shippo's test token (shippo_test_...) returns realistic simulated rates without actually creating shipments or charging you. Use this for all development and testing.
Expected result: The API route successfully calls Shippo and returns an array of carrier rates sorted by price. Each rate includes the carrier name, service level, estimated delivery days, and price.
Create the Label Purchase API Route
Create the Label Purchase API Route
If your app needs to purchase actual shipping labels (not just display rates), create a separate route at app/api/shippo/label/route.ts. This route accepts the rate object ID from the rates endpoint and calls Shippo's transactions endpoint to purchase the label. Purchasing a label in Shippo's API is called creating a Transaction. You pass the rate's object_id (the unique ID of the specific carrier/service/price combination you want to purchase) and Shippo charges your Shippo account or your connected carrier account for the label cost. The response includes a label_url (a PDF link) and a tracking_number. Label purchases are real charges in production (using your live Shippo token). In test mode (using shippo_test_ tokens), label creation returns realistic-looking but non-functional label URLs and tracking numbers — this is perfect for testing your UI flow without incurring costs. The test/production switch is entirely controlled by which API token you use. An important operational consideration for label purchase in V0 apps: you should record the transaction ID and label URL in your database immediately after creation. Labels cannot be easily re-fetched later without storing the transaction ID. If the database write fails after the label is purchased, you will have charged the carrier account but lost the label URL. Consider wrapping the label purchase and database write in a try-catch that logs failures for manual recovery. For refunds: carriers have different windows for label refund eligibility (USPS: 28 days if never scanned, UPS/FedEx: 15-30 days). Shippo's API has a refund endpoint at /transactions/{id}/refund, but you will need to build that flow separately if your app needs it.
Add a Next.js API route at app/api/shippo/label/route.ts that accepts POST requests with a rateObjectId field. Call the Shippo transactions endpoint at https://api.goshippo.com/transactions/ with the rate object ID, use the SHIPPO_API_TOKEN from environment variables, and return the label_url, tracking_number, and tracking_url from the response.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23interface ShippoTransaction {4 object_id: string;5 status: string;6 label_url: string;7 tracking_number: string;8 tracking_url_provider: string;9 messages: Array<{ code: string; text: string; source: string }>;10}1112export async function POST(request: NextRequest) {13 try {14 const { rateObjectId } = await request.json();1516 if (!rateObjectId) {17 return NextResponse.json(18 { error: 'rateObjectId is required' },19 { status: 400 }20 );21 }2223 const shippoResponse = await fetch('https://api.goshippo.com/transactions/', {24 method: 'POST',25 headers: {26 Authorization: `Shippo ${process.env.SHIPPO_API_TOKEN}`,27 'Content-Type': 'application/json',28 },29 body: JSON.stringify({30 rate: rateObjectId,31 label_file_type: 'PDF',32 async: false,33 }),34 });3536 if (!shippoResponse.ok) {37 const errorData = await shippoResponse.text();38 console.error('Shippo label error:', errorData);39 return NextResponse.json(40 { error: 'Failed to purchase label' },41 { status: shippoResponse.status }42 );43 }4445 const transaction: ShippoTransaction = await shippoResponse.json();4647 if (transaction.status !== 'SUCCESS') {48 const errorMessages = transaction.messages49 .map((m) => m.text)50 .join(', ');51 return NextResponse.json(52 { error: `Label creation failed: ${errorMessages}` },53 { status: 422 }54 );55 }5657 return NextResponse.json({58 labelUrl: transaction.label_url,59 trackingNumber: transaction.tracking_number,60 trackingUrl: transaction.tracking_url_provider,61 transactionId: transaction.object_id,62 });63 } catch (error) {64 console.error('Shippo label route error:', error);65 return NextResponse.json(66 { error: 'Internal server error' },67 { status: 500 }68 );69 }70}Pro tip: Always store the transactionId returned by this route in your database alongside the order. You will need it if you ever need to void or refund a label.
Expected result: The label route successfully purchases a Shippo label and returns the PDF label URL, tracking number, and Shippo transaction ID. In test mode, the label URL is a real PDF but the label cannot be scanned at a carrier facility.
Add Your Shippo API Token to Vercel
Add Your Shippo API Token to Vercel
Your API routes read SHIPPO_API_TOKEN from environment variables. You need to add this to Vercel so the serverless functions can authenticate with Shippo's API. Go to your Vercel Dashboard and open your project. Click the Settings tab at the top, then click Environment Variables in the left sidebar. In the form, enter the key name as SHIPPO_API_TOKEN and paste your Shippo API token as the value. Your test token starts with shippo_test_ and your live token starts with shippo_live_ — they are found in goshippo.com → API → API Keys. Do not add a NEXT_PUBLIC_ prefix to this variable. The Shippo API token is a server-side secret that should never be exposed in the browser's JavaScript bundle. Only your Next.js API routes running on Vercel's serverless infrastructure will access this variable. Set the environment scope to Production, Preview, and Development so the variable is available across all deployment contexts. After adding the variable, click Save. You need to trigger a redeployment — push any commit to your GitHub repository — for the environment variable to become available in your functions. For local development, create a .env.local file in your project root and add SHIPPO_API_TOKEN=shippo_test_your_token_here. This file should already be in your .gitignore in V0-generated projects. The .env.local file is never sent to Vercel — it exists only on your local machine for development. Shippo has two completely separate token environments: test mode and live mode. Test tokens simulate all API calls without real carrier communication or charges. When you are ready to go live, update the SHIPPO_API_TOKEN in Vercel to your live token and redeploy.
Pro tip: Shippo test mode returns realistic carrier rates (USPS, UPS, FedEx) that reflect real pricing algorithms, making it excellent for building and demoing your app before going live.
Expected result: Vercel Dashboard shows SHIPPO_API_TOKEN saved in environment variables. After redeployment, your API routes successfully authenticate with Shippo's API and return real carrier rates.
Test the Full Shipping Flow
Test the Full Shipping Flow
With your environment variable configured and your V0 app deployed to Vercel, test the complete shipping flow end-to-end. Navigate to the page with your V0-generated shipping rate component, enter a real origin ZIP code, a destination ZIP code, and a package weight, then click Get Rates. In test mode, Shippo returns realistic rates from USPS, UPS, and FedEx. The rates reflect real pricing logic but no actual carrier accounts are needed or charged. A typical rate request returns 5-15 service options across carriers, ranging from inexpensive ground services to expedited overnight options. Verify the response structure by checking your Vercel function logs. Go to your Vercel Dashboard, click your project, then Functions, then the log for your /api/shippo/route. You should see the incoming request body and the Shippo API response. If the rates array is empty, check that your Authorization header is formatted as Shippo <token> (with a capital S and a space, not Bearer). For production readiness, you will need a Shippo account with carrier accounts connected. For USPS, Shippo provides a shared USPS account that works automatically — you get Shippo's negotiated USPS rates without needing your own USPS business account. For UPS and FedEx, you need to connect your own carrier accounts in goshippo.com → Carriers → Connect Carrier. This is a one-time setup step. For complex shipping workflows — including international shipments with customs forms, multi-package shipments, or return label flows — RapidDev's team can help configure the full integration with your V0 app.
Add a success state to the shipping rate component: after the user selects a rate and clicks 'Purchase Label', show a confirmation card with the carrier name, service, tracking number, and a 'Download Label (PDF)' button that opens the label URL in a new tab. Also add a 'Ship Another' button to reset the form.
Paste this in V0 chat
Pro tip: Use ZIP code 10001 (New York) as origin and 90210 (Beverly Hills) as destination for testing — this cross-country route reliably returns rates from all major carriers.
Expected result: The complete shipping flow works end-to-end in Vercel's deployed environment: rates load from Shippo within 2-4 seconds, label purchase returns a valid PDF URL, and the tracking number is displayed correctly.
Common use cases
E-Commerce Checkout Shipping Rate Comparison
A founder uses V0 to generate a checkout flow that shows real-time carrier rates from USPS, UPS, and FedEx before the customer pays. The customer enters their address, selects a shipping option with the displayed price, and that selection is stored with the order. The API route calls Shippo's rate endpoint with the origin, destination, and parcel dimensions to retrieve live rates.
Create a checkout shipping step that shows a table of shipping options. Each row displays a carrier logo (USPS, UPS, FedEx), service name (e.g. 'Priority Mail'), estimated delivery time, and price. The table should load dynamically from /api/shippo/rates and show a loading skeleton while fetching. Add a radio button on each row to select the shipping method.
Copy this prompt to try it in V0
Order Fulfillment Label Generator
An e-commerce operator uses V0 to build an internal order dashboard where staff can generate shipping labels with one click. After reviewing an order's address and package details, clicking 'Create Label' calls the Shippo API to purchase a label at the selected rate. The API route returns a label URL that opens in a new tab for printing.
Build an order detail page with a 'Shipping' section. Show the recipient address, package weight/dimensions fields, a carrier rate selector that loads from /api/shippo/rates, and a 'Purchase Label' button that POSTs to /api/shippo/label. After the label is created, show a green success state with a 'Download Label (PDF)' link that opens the returned label URL.
Copy this prompt to try it in V0
Package Tracking Status Dashboard
A logistics app built with V0 shows customers the current tracking status of their shipments. Customers enter a tracking number and carrier, and the app calls Shippo's tracking API to retrieve the latest status, estimated delivery date, and location history. V0 generates the tracking timeline UI component.
Create a shipment tracking page. Include a search input for tracking number and a carrier dropdown (USPS, UPS, FedEx, DHL). On submit, fetch from /api/shippo/track and display the tracking results: current status badge (In Transit, Delivered, etc.), estimated delivery date, and a vertical timeline of tracking events with timestamps and locations.
Copy this prompt to try it in V0
Troubleshooting
Shippo API returns 401 Unauthorized
Cause: The Authorization header format is incorrect. Shippo requires the format 'Shippo <token>' with a capital S and a single space — not 'Bearer <token>' which is the more common format.
Solution: Check your API route's Authorization header. It must be exactly: `Authorization: 'Shippo ' + process.env.SHIPPO_API_TOKEN` — note the capital S in Shippo and the space before the token. Also verify your SHIPPO_API_TOKEN environment variable is set correctly in Vercel and that you have redeployed since adding it.
1// WRONG2Authorization: `Bearer ${process.env.SHIPPO_API_TOKEN}`34// CORRECT5Authorization: `Shippo ${process.env.SHIPPO_API_TOKEN}`API route returns empty rates array even though Shippo responds with 200
Cause: The shipment was created in async mode (the default) and the carriers have not finished responding yet. Async mode returns an object with an empty rates array immediately and populates it asynchronously.
Solution: Add async: false to your Shippo shipment request body. This makes Shippo wait for all carrier responses before returning, typically adding 1-3 seconds to the response time but ensuring you always get a populated rates array.
1// Add async: false to force synchronous rate fetching2body: JSON.stringify({3 address_from: { ... },4 address_to: { ... },5 parcels: [ ... ],6 async: false // Add this line7})Transaction status is ERROR and messages contains 'INVALID_ADDRESS'
Cause: The destination address ZIP code is invalid, missing, or formatted incorrectly. Shippo validates addresses on label creation, and some valid-looking ZIPs may not match a real carrier service area.
Solution: Use Shippo's Address Validation endpoint to validate addresses before requesting rates. The endpoint is GET /api/v2/addresses/{object_id}/validate. For user-entered addresses, add client-side ZIP format validation (5 digits for US) before calling the rates API.
NEXT_PUBLIC_SHIPPO_API_TOKEN is undefined in the API route
Cause: You accidentally added NEXT_PUBLIC_ prefix to the Shippo token variable. Variables with this prefix are only available client-side at build time, not in server-side API routes in the way you might expect — and worse, they expose your token in the browser bundle.
Solution: Rename the environment variable in Vercel to SHIPPO_API_TOKEN (without any prefix). Update your API route to use process.env.SHIPPO_API_TOKEN. Redeploy to pick up the corrected variable name. Never add NEXT_PUBLIC_ to API tokens.
Best practices
- Always use async: false in Shippo rate requests to ensure you receive populated carrier rates immediately rather than polling for async results.
- Store the Shippo transaction ID in your database immediately after label purchase — you cannot retrieve it later without it, and it is required for voids and refunds.
- Use Shippo's test tokens (shippo_test_) for all development and staging — they return realistic rates without real carrier charges.
- Never add NEXT_PUBLIC_ prefix to SHIPPO_API_TOKEN — your server-only API route is the only code that should touch this value.
- Sort rates by price in your API route before returning to the frontend, and consider filtering to show only the 3-5 most relevant options rather than all 15+ carrier services.
- Validate address fields (ZIP format, required fields) client-side before calling the rates API to reduce unnecessary Shippo API calls and improve user experience.
- Set up Vercel function timeout appropriately — synchronous Shippo rate requests can take 3-5 seconds when querying multiple carriers simultaneously.
- Connect your own carrier accounts in Shippo (UPS, FedEx) to get commercial pricing discounts that are typically 30-70% below retail rates.
Alternatives
ShipStation is a better choice if your team wants a ready-made fulfillment UI rather than a custom V0 app — it provides its own order management dashboard without requiring API development.
AfterShip is an alternative if your primary need is shipment tracking and delivery notifications rather than rate comparison and label creation.
The FedEx API is an alternative if you only need FedEx-specific shipping and want a direct carrier integration without Shippo as an intermediary.
The UPS API is a direct integration option if your business exclusively uses UPS and you want to avoid the additional Shippo middleware layer.
Frequently asked questions
Does Shippo charge per API call or per label?
Shippo is free for rate lookups and address validation — you only pay when you purchase a label. Label pricing depends on the carrier and service level, and Shippo adds a small per-label fee (starting at $0.05) on top of the carrier cost. Their Pay As You Go plan has no monthly fees, making it ideal for V0 apps with variable shipping volume.
Can I use Shippo with V0 for international shipping?
Yes, Shippo supports international shipping to 220+ countries. For international shipments, you will need to include customs declaration information in your API request (item descriptions, values, HS codes). The API route pattern is the same, but the shipment object requires additional customs fields. V0 can generate the customs form UI, and you extend the API route to pass the customs data to Shippo.
How do I switch from Shippo test mode to live mode?
The only change needed is your API token. In Vercel Dashboard → Settings → Environment Variables, update SHIPPO_API_TOKEN from your test token (shippo_test_...) to your live token (shippo_live_...). Redeploy your Vercel project. Your code does not need any other changes — Shippo's test and live APIs are identical in structure, just authenticated separately.
Why does Shippo show different rates than the carrier's website?
Shippo negotiates volume discounts with carriers on behalf of all its customers. USPS rates through Shippo use Commercial Base Pricing, which is lower than retail rates. For UPS and FedEx, rates depend on whether you connect your own carrier account (you get your contracted rates) or use Shippo's rates. In either case, Shippo rates are typically equal to or better than what you would pay buying labels directly.
Can V0 generate the shipping rate UI automatically?
Yes. V0 is excellent at generating shipping UI components including rate comparison tables, carrier selection radio buttons, and label download interfaces. Be specific in your V0 prompt about the data structure — tell it the API response shape (carrier name, service name, price, estimated days) so it generates the correct component bindings. You can also share the API response JSON with V0 and ask it to build a display component around that shape.
What happens if my Vercel function times out during a Shippo rate request?
Synchronous Shippo rate requests can take 3-6 seconds when querying multiple carriers. Vercel's default Hobby plan function timeout is 10 seconds, which is usually sufficient. If you hit timeouts, upgrade to a Pro plan (60-second default) or implement async mode with a polling pattern. You can also reduce latency by limiting the carriers parameter in the Shippo request to only the 2-3 carriers you want to display.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation