# How to Integrate Bolt.new with eBay API

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 30 minutes
- Last updated: April 2026

## TL;DR

To integrate eBay's REST APIs with Bolt.new, register an eBay developer app, generate OAuth tokens server-side using client credentials flow, and proxy all API calls through a Next.js API route that keeps your client secret out of browser code. Use the Browse API for searching and displaying listings, and the Sell API for managing inventory. OAuth token exchange must happen server-side — the WebContainer-safe pattern proxies everything through an API route before deployment.

## Build eBay Listing Management Tools and Price Comparison Apps with Bolt.new

eBay's modern REST API suite communicates entirely over HTTPS with JSON payloads, making it architecturally compatible with Bolt.new's WebContainer runtime. Every product search, listing query, inventory update, and order operation travels over standard HTTP — no TCP sockets, no native binaries, no special runtimes. You can build price comparison tools, seller dashboards, order management interfaces, and competitor research apps, all runnable inside Bolt's preview during development.

eBay's API suite is divided into two main families: the Browse API for buyer-facing search and product discovery, and the Sell APIs for seller-facing inventory, listings, and order management. The Browse API uses application-only OAuth (client credentials grant), which requires only your app credentials and works without user interaction. The Sell APIs may additionally require user OAuth (authorization code grant) where a seller must log in and authorize your app — this OAuth flow requires a registered callback URL and needs a deployed app URL rather than Bolt's preview.

The most important security constraint for eBay in Bolt is that OAuth token exchange must happen server-side. The client credentials grant requires sending your `client_id:client_secret` encoded as Base64 Basic Auth — exposing the client secret in browser code would allow anyone to make API calls billed against your eBay developer account. Bolt's Next.js API route pattern solves this perfectly: your React components call your own `/api/ebay` route, which securely handles token acquisition and eBay API proxying on the server side.

## Before you start

- A Bolt.new account with a Next.js project
- An eBay developer account (free at developer.ebay.com)
- An eBay developer application registered in the developer portal (provides client ID and client secret)
- Knowledge of whether you need Browse API only (application OAuth) or Sell API (user OAuth requiring deployment)

## Step-by-step guide

### 1. Register an eBay Developer Application

The eBay developer program is free to join and gives you access to sandbox and production API credentials. Go to developer.ebay.com and sign in with your eBay account (or create one). Navigate to 'Get Started' and then 'Register for eBay Developer Program.' Once registered, go to the developer portal at developer.ebay.com/my/keys and create an application.

Click 'Create a keyset' and give your application a descriptive name. eBay provides two sets of credentials: Sandbox (for testing with fake data) and Production (for real eBay data). Each set includes three values: App ID (this is the `client_id`), Cert ID (this is the `client_secret`), and Dev ID (a unique developer identifier you typically do not use directly in API calls). For initial development, use Sandbox credentials — eBay's sandbox environment has pre-populated test data that looks realistic.

For the Sell API features, you will also need to configure an RuName (Redirect URL Name) in the developer portal under 'User Tokens.' The RuName is a registered alias for your OAuth callback URL. For development, eBay provides a default 'Accept URL' for the sandbox that you can use for testing. For production user OAuth, register your actual deployed URL. Write down your App ID (client_id), Cert ID (client_secret), and for user OAuth your RuName — you will need all three in your .env file.

**Expected result:** You have eBay developer application credentials: App ID (client_id) and Cert ID (client_secret) for both Sandbox and Production environments, saved securely.

### 2. Implement OAuth Client Credentials Grant in a Next.js API Route

eBay's OAuth 2.0 implementation requires the client credentials grant for application-level access (Browse API) and the authorization code grant for user-level access (Sell API). The client credentials flow is simpler: you send your client_id and client_secret to eBay's token endpoint and receive an access token. This token expires (typically after 2 hours) and must be refreshed. Critically, this entire exchange must happen server-side — never in a React component or any client-side code.

The token request uses HTTP Basic Auth with `client_id:client_secret` Base64-encoded, and a `grant_type=client_credentials` body. eBay's token endpoint for sandbox is `https://api.sandbox.ebay.com/identity/v1/oauth2/token` and for production is `https://api.ebay.com/identity/v1/oauth2/token`. The scope parameter specifies which APIs your token grants access to — for the Browse API, use `https://api.ebay.com/oauth/api_scope`.

For production code, cache the token and reuse it until it expires rather than fetching a new one on every request. You can store the token in a module-level variable (server memory) or in a cache service. The token response includes `expires_in` (seconds) which tells you when to refresh. In Bolt's development environment with Next.js, a module-level cache works fine for testing. For production deployments on serverless platforms (Netlify Functions, Vercel Edge), be aware that each function invocation may be a fresh process, so module-level caching may not persist across requests — implement a proper cache service or accept the small overhead of re-fetching tokens.

```
// app/api/ebay/route.ts
import { NextResponse } from 'next/server';

const EBAY_CLIENT_ID = process.env.EBAY_CLIENT_ID;
const EBAY_CLIENT_SECRET = process.env.EBAY_CLIENT_SECRET;
const IS_SANDBOX = process.env.EBAY_SANDBOX === 'true';

const EBAY_API_BASE = IS_SANDBOX
  ? 'https://api.sandbox.ebay.com'
  : 'https://api.ebay.com';
const EBAY_TOKEN_URL = `${EBAY_API_BASE}/identity/v1/oauth2/token`;

// Module-level token cache (works for dev; use Redis/KV for production serverless)
let cachedToken: { value: string; expiresAt: number } | null = null;

async function getApplicationToken(): Promise<string> {
  if (cachedToken && cachedToken.expiresAt > Date.now() + 60_000) {
    return cachedToken.value;
  }

  const credentials = Buffer.from(`${EBAY_CLIENT_ID}:${EBAY_CLIENT_SECRET}`).toString('base64');

  const response = await fetch(EBAY_TOKEN_URL, {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${credentials}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: 'grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope',
  });

  if (!response.ok) {
    throw new Error(`eBay token request failed: ${response.statusText}`);
  }

  const data = await response.json();
  cachedToken = {
    value: data.access_token,
    expiresAt: Date.now() + data.expires_in * 1000,
  };
  return cachedToken.value;
}

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const action = searchParams.get('action');

  try {
    const token = await getApplicationToken();

    if (action === 'search') {
      const query = searchParams.get('q') || '';
      const limit = searchParams.get('limit') || '20';
      const minPrice = searchParams.get('minPrice');
      const maxPrice = searchParams.get('maxPrice');
      const condition = searchParams.get('condition'); // NEW, USED, etc.

      const filters: string[] = [];
      if (minPrice || maxPrice) {
        filters.push(`price:[${minPrice || '*'}..${maxPrice || '*'}],priceCurrency:USD`);
      }
      if (condition) filters.push(`conditions:{${condition}}`);

      const params = new URLSearchParams({
        q: query,
        limit,
        ...(filters.length > 0 && { filter: filters.join(',') }),
        fieldgroups: 'EXTENDED',
        sort: 'price',
      });

      const res = await fetch(
        `${EBAY_API_BASE}/buy/browse/v1/item_summary/search?${params}`,
        { headers: { 'Authorization': `Bearer ${token}`, 'X-EBAY-C-MARKETPLACE-ID': 'EBAY_US' } }
      );

      const data = await res.json();
      return NextResponse.json(data);
    }

    return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Unknown error';
    return NextResponse.json({ error: message }, { status: 500 });
  }
}
```

**Expected result:** The API route acquires an eBay OAuth token and successfully returns search results. The token is cached so subsequent requests are fast.

### 3. Configure Environment Variables and Connect to Bolt

Add your eBay credentials to the `.env` file in your Bolt project root. Server-side credentials in Next.js do not need a `NEXT_PUBLIC_` prefix — they are accessible only in API routes and server components, never in client-side JavaScript bundles.

The three variables you need are `EBAY_CLIENT_ID` (your App ID from the developer portal), `EBAY_CLIENT_SECRET` (your Cert ID), and `EBAY_SANDBOX` set to `true` for development. Treat the Cert ID (client secret) as highly sensitive — anyone with your client secret can make API calls under your developer account and potentially access or modify eBay seller data your account has been authorized for.

After saving the `.env` file, restart the Bolt development server to pick up the new values. In the Bolt terminal, press Ctrl+C to stop the current server and run `npm run dev` to restart. Your API route should now successfully acquire tokens from eBay's sandbox and execute search queries. Test the endpoint by navigating to `/api/ebay?action=search&q=laptop&limit=5` in the preview — you should see JSON results with eBay sandbox listings.

```
# .env
EBAY_CLIENT_ID=your-ebay-app-id
EBAY_CLIENT_SECRET=your-ebay-cert-id
EBAY_SANDBOX=true
```

**Expected result:** Environment variables are configured. The eBay API route returns sandbox search results when tested with a sample query.

### 4. Build the eBay Listings Display Component

With the API route working, build a React component that searches eBay and displays results in a data table or card grid. The component calls your `/api/ebay` route (not eBay directly) and renders the listing data with images, prices, conditions, and seller information.

eBay's Browse API item_summary search returns items with a consistent schema: `itemId`, `title`, `price` (amount and currency), `condition`, `image` (imageUrl), `itemWebUrl`, `seller` (username and feedbackPercentage), and `shippingOptions`. Map these into your React component with appropriate styling.

For condition display, eBay uses standardized condition strings: 'NEW', 'LIKE_NEW', 'EXCELLENT', 'VERY_GOOD', 'GOOD', 'ACCEPTABLE', 'FOR_PARTS_OR_NOT_WORKING'. Add visual badges with appropriate colors (green for NEW, yellow for GOOD, gray for PARTS). For price display, format the currency correctly using JavaScript's `Intl.NumberFormat` — eBay returns prices as strings like '29.99' with a separate currency code 'USD'.

The Buy Browse API returns up to 10,000 items per search with pagination via `offset` and `limit` parameters. Implement 'Load More' pagination using the `total` count from the response and incrementing the `offset` parameter. Bolt's AI can generate this component in detail — provide the response schema in your prompt for best results.

```
// components/EbaySearchResults.tsx
import { useState } from 'react';

interface EbayItem {
  itemId: string;
  title: string;
  price: { value: string; currency: string };
  condition: string;
  image?: { imageUrl: string };
  itemWebUrl: string;
  seller: { username: string; feedbackPercentage: string };
}

interface SearchFilters {
  query: string;
  condition?: string;
  minPrice?: string;
  maxPrice?: string;
}

export function EbaySearchResults() {
  const [filters, setFilters] = useState<SearchFilters>({ query: '' });
  const [items, setItems] = useState<EbayItem[]>([]);
  const [total, setTotal] = useState(0);
  const [offset, setOffset] = useState(0);
  const [loading, setLoading] = useState(false);
  const LIMIT = 20;

  const search = async (newOffset = 0) => {
    setLoading(true);
    const params = new URLSearchParams({
      action: 'search',
      q: filters.query,
      limit: String(LIMIT),
      offset: String(newOffset),
      ...(filters.condition && { condition: filters.condition }),
      ...(filters.minPrice && { minPrice: filters.minPrice }),
      ...(filters.maxPrice && { maxPrice: filters.maxPrice }),
    });
    const res = await fetch(`/api/ebay?${params}`);
    const data = await res.json();
    if (newOffset === 0) {
      setItems(data.itemSummaries || []);
    } else {
      setItems(prev => [...prev, ...(data.itemSummaries || [])]);
    }
    setTotal(data.total || 0);
    setOffset(newOffset + LIMIT);
    setLoading(false);
  };

  const conditionColor = (c: string) => {
    if (c === 'NEW') return 'bg-green-100 text-green-800';
    if (c.includes('USED') || c.includes('LIKE_NEW') || c.includes('EXCELLENT')) return 'bg-yellow-100 text-yellow-800';
    return 'bg-gray-100 text-gray-800';
  };

  const formatPrice = (price: EbayItem['price']) =>
    new Intl.NumberFormat('en-US', { style: 'currency', currency: price.currency }).format(
      parseFloat(price.value)
    );

  return (
    <div className="p-4">
      <div className="flex gap-2 mb-4 flex-wrap">
        <input
          placeholder="Search eBay..."
          value={filters.query}
          onChange={e => setFilters(f => ({ ...f, query: e.target.value }))}
          onKeyDown={e => e.key === 'Enter' && search()}
          className="flex-1 min-w-48 border rounded px-3 py-2"
        />
        <select
          value={filters.condition || ''}
          onChange={e => setFilters(f => ({ ...f, condition: e.target.value || undefined }))}
          className="border rounded px-3 py-2"
        >
          <option value="">Any condition</option>
          <option value="NEW">New</option>
          <option value="USED">Used</option>
        </select>
        <button onClick={() => search()} className="bg-yellow-400 px-4 py-2 rounded font-medium">
          Search
        </button>
      </div>
      {total > 0 && <p className="text-sm text-gray-500 mb-3">{total.toLocaleString()} results</p>}
      <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
        {items.map(item => (
          <div key={item.itemId} className="border rounded p-3">
            {item.image && (
              <img src={item.image.imageUrl} alt={item.title} className="w-full h-32 object-contain mb-2" />
            )}
            <p className="text-sm font-medium line-clamp-2 mb-1">{item.title}</p>
            <p className="text-lg font-bold text-green-700">{formatPrice(item.price)}</p>
            <span className={`text-xs px-2 py-0.5 rounded ${conditionColor(item.condition)}`}>
              {item.condition.replace(/_/g, ' ')}
            </span>
            <p className="text-xs text-gray-500 mt-1">{item.seller.username} ({item.seller.feedbackPercentage}%)</p>
            <a href={item.itemWebUrl} target="_blank" rel="noopener noreferrer"
              className="text-xs text-blue-600 underline mt-1 block">
              View on eBay
            </a>
          </div>
        ))}
      </div>
      {items.length < total && (
        <button onClick={() => search(offset)} disabled={loading}
          className="mt-4 w-full border rounded py-2 text-sm hover:bg-gray-50">
          {loading ? 'Loading...' : `Load more (${total - items.length} remaining)`}
        </button>
      )}
    </div>
  );
}
```

**Expected result:** The search component displays eBay listings as a product card grid with images, prices, conditions, and seller info. Pagination loads additional results without reloading the page.

### 5. Deploy and Test in Production Environment

To switch from eBay sandbox to production and to enable any webhook-based order notifications, deploy your Bolt project to Netlify or Bolt Cloud. eBay's notification system (eBay Marketplace Account Deletion Notification and other event notifications) sends POST requests to a publicly accessible URL — these cannot reach Bolt's WebContainer during development since the preview URL is not a stable public endpoint.

For production deployment: push your project to GitHub from Bolt's Git panel, then import the repository in Netlify. In Netlify's Site configuration > Environment variables, change `EBAY_CLIENT_ID` and `EBAY_CLIENT_SECRET` to your Production credentials (not sandbox), and set `EBAY_SANDBOX=false`. The build command for Next.js is `npm run build` and the output directory is `.next`.

For user OAuth (Sell API): once deployed, go to your eBay developer application settings and update the Production RuName to point to your Netlify URL's OAuth callback path (e.g., `https://your-app.netlify.app/api/ebay/callback`). eBay's production API requires that the redirect URL for user authorization exactly matches a registered RuName — mismatches cause the OAuth flow to fail with an 'invalid redirect_uri' error.

```
// app/api/ebay/health/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  const clientId = process.env.EBAY_CLIENT_ID;
  const clientSecret = process.env.EBAY_CLIENT_SECRET;

  if (!clientId || !clientSecret) {
    return NextResponse.json(
      { status: 'error', message: 'eBay credentials not configured' },
      { status: 500 }
    );
  }

  try {
    const isSandbox = process.env.EBAY_SANDBOX === 'true';
    const tokenUrl = isSandbox
      ? 'https://api.sandbox.ebay.com/identity/v1/oauth2/token'
      : 'https://api.ebay.com/identity/v1/oauth2/token';
    const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
    const res = await fetch(tokenUrl, {
      method: 'POST',
      headers: { 'Authorization': `Basic ${credentials}`, 'Content-Type': 'application/x-www-form-urlencoded' },
      body: 'grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope',
    });
    if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
    return NextResponse.json({
      status: 'ok',
      environment: isSandbox ? 'sandbox' : 'production',
    });
  } catch (err) {
    return NextResponse.json(
      { status: 'error', message: err instanceof Error ? err.message : 'Unknown error' },
      { status: 500 }
    );
  }
}
```

**Expected result:** The app is deployed to Netlify with production eBay credentials. The /api/ebay/health endpoint returns { status: 'ok', environment: 'production' } confirming proper configuration.

## Best practices

- Cache eBay OAuth tokens in server memory for their full 2-hour lifetime rather than fetching a new token on every request — token requests count against rate limits and add latency to every API call.
- Never expose EBAY_CLIENT_SECRET in any client-side code, environment variable with NEXT_PUBLIC_ prefix, or commit it to a public GitHub repository — treat it with the same sensitivity as a database password.
- Use eBay's sandbox environment throughout development and only switch to production credentials on your deployed site — this prevents accidentally listing real items or modifying production data during development.
- Implement response caching for Browse API search results since product listings change slowly — cache results for 5-15 minutes to stay well within eBay's API rate limits and improve response times.
- Request only the Browse API fields you render using the 'fieldgroups' parameter; 'COMPACT' returns basic listing data while 'EXTENDED' adds shipping, returns, and seller details — choose based on what your UI displays.
- Handle eBay's pagination correctly using the 'offset' parameter rather than loading all results at once — searches can return millions of items and fetching them all would exhaust your rate limit immediately.
- Validate that your app handles the 'condition' field gracefully since eBay returns both standardized condition IDs and human-readable labels, and the format can differ between sandbox and production.

## Use cases

### Product Price Comparison Tool

Build a price research tool that searches eBay for a product and displays listings sorted by price, condition, and seller rating. Useful for resellers and buyers comparing eBay prices against other platforms or their own inventory costs. Uses the Browse API's item search endpoint with filters for condition, price range, and location.

Prompt example:

```
Build an eBay price comparison tool. Create a Next.js API route at /api/ebay/search that uses eBay's Browse API to search for items. Handle OAuth client credentials token acquisition server-side using EBAY_CLIENT_ID and EBAY_CLIENT_SECRET from process.env. Return listings with title, price, condition, seller feedback score, item URL, and main image. Build a React component with a search input, price range filter, condition dropdown, and results displayed in a sortable data table.
```

### Seller Listing Management Dashboard

Create a dashboard for eBay sellers to view their active listings, monitor prices, check inventory quantities, and quickly update prices or end listings. Connects to the Sell API Inventory and Listing endpoints. Particularly useful for high-volume sellers managing dozens or hundreds of active listings who need faster workflows than eBay's native interface.

Prompt example:

```
Build an eBay seller dashboard. Create Next.js API routes for: 1) GET /api/ebay/listings to fetch the authenticated seller's active listings using the Sell Inventory API, 2) PUT /api/ebay/listings/[listingId] to update a listing's price or quantity, 3) DELETE /api/ebay/listings/[listingId] to end a listing. Handle OAuth user token from a stored cookie. Display listings in a data grid with inline editing for price and quantity. Add a note that user OAuth requires the deployed app URL.
```

### eBay Category Research and Analytics Widget

Build a category research tool that searches eBay for products in a specific category, aggregates pricing data (min, max, average, median), and shows the distribution of sold vs active listings. Helps sellers identify pricing opportunities and understand market depth before listing new products.

Prompt example:

```
Build an eBay market research tool using the Browse API. Create an API route that searches for a product query within a specific category ID and returns aggregated stats: total results count, price range (min/max), average price, and the top 20 listings sorted by price. Use EBAY_CLIENT_ID and EBAY_CLIENT_SECRET for client credentials OAuth. Display results as a price histogram chart and a listings table.
```

## Troubleshooting

### OAuth token request returns 401 with 'Invalid client credentials'

Cause: The EBAY_CLIENT_ID or EBAY_CLIENT_SECRET is incorrect, or you are using Sandbox credentials against the production token endpoint (or vice versa).

Solution: Verify your App ID and Cert ID in the eBay developer portal at developer.ebay.com/my/keys. Sandbox IDs contain '-SBX-' and production IDs contain '-PRD-'. Ensure EBAY_SANDBOX in your .env matches the credentials you are using.

### Browse API search returns 200 but 'itemSummaries' is undefined or missing from the response

Cause: The search query returned zero results (common in sandbox with unusual queries), or the response schema has changed. The eBay Browse API returns 'itemSummaries' only when there are results — it omits the field entirely for empty result sets.

Solution: Default to an empty array when accessing itemSummaries: const items = data.itemSummaries || []. In sandbox, try common search terms like 'laptop', 'phone', or 'watch' which have pre-populated test data.

```
const data = await res.json();
const items: EbayItem[] = data.itemSummaries ?? [];
const total: number = data.total ?? 0;
```

### API calls succeed in Bolt's preview but fail after deploying to Netlify with CORS errors

Cause: The client-side React component is calling the eBay API directly instead of going through the /api/ebay Next.js route, or the API route URL is hardcoded to localhost.

Solution: Verify all eBay API calls in your React components use relative URLs like /api/ebay (not https://api.ebay.com or http://localhost:3000/api/ebay). Relative URLs work correctly in both development and production without any changes.

```
// Correct: relative URL works in both dev and production
const res = await fetch('/api/ebay?action=search&q=' + query);

// Incorrect: hardcoded localhost breaks in production
const res = await fetch('http://localhost:3000/api/ebay?action=search&q=' + query);
```

### eBay webhook notifications are not arriving — order events happen on eBay but nothing reaches the app

Cause: The webhook endpoint URL is the Bolt WebContainer preview URL, which is not publicly accessible. WebContainers cannot receive incoming HTTP connections.

Solution: Deploy the app to Netlify or Bolt Cloud first, then register the deployed URL as the notification endpoint in your eBay developer account under Application Settings > Notifications. The webhook URL must be a stable, publicly accessible HTTPS endpoint.

## Frequently asked questions

### Does Bolt.new have a native eBay integration?

No — Bolt.new does not have a built-in eBay connector. You build the integration manually using a Next.js API route as a server-side proxy that handles OAuth and API calls. Bolt's AI assistant generates all the boilerplate code from a single prompt, so setup is fast even without a native integration.

### Can I use the eBay API during development in Bolt's preview without deploying?

Yes — the Browse API for searching and viewing eBay listings works fully in Bolt's WebContainer preview. Your Next.js API route makes outbound HTTPS calls to eBay's sandbox, which the WebContainer handles correctly. The only features requiring deployment are webhook notifications (incoming HTTP requests) and user OAuth flows where eBay needs to redirect to a stable callback URL.

### What is the difference between eBay's client_id (App ID) and client_secret (Cert ID)?

eBay uses Atlassian-style naming in its developer portal. The 'App ID' is what most OAuth implementations call client_id — it identifies your application publicly. The 'Cert ID' is what most OAuth implementations call client_secret — it authenticates your app server-side and must never be exposed in client code. The 'Dev ID' is a unique identifier for your developer account that you typically do not use in API calls directly.

### How do I switch from eBay sandbox to production in my Bolt app?

In your .env file, change EBAY_CLIENT_ID to your Production App ID (contains -PRD-), EBAY_CLIENT_SECRET to your Production Cert ID, and set EBAY_SANDBOX=false. The API URLs switch automatically if your code uses the IS_SANDBOX conditional. For Netlify deployment, update the same environment variables in Netlify's Site configuration. Always test thoroughly in sandbox before making this switch.

### What are eBay's API rate limits?

eBay's Browse API allows 5,000 calls per day for new applications on the default tier. The Sell APIs have different limits depending on the seller's account level. Rate limit headers in the response (X-EBAY-C-REQUEST-LIMIT-REMAINING) show remaining quota. For higher limits, apply for increased access through the eBay developer portal. Implement caching to stay well within limits.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/ebay-api
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/ebay-api
