Back to blog

eBay Item API Guide: Look Up a Listing by Item ID

By SoldFetch Engineering9 min read

A practical guide to retrieving eBay listing details, preserving price and marketplace context, and handling missing fields, caching, and API errors.

eBay Item API Guide: Look Up a Listing by Item ID

An item lookup turns a listing identifier into a record your application can use: a title, displayed price, currency, condition, images, and seller context. The useful part is keeping those fields attached to the listing they describe. A price without its item ID, storefront, and observation time is difficult to compare or explain later.

This guide walks through the SoldFetch item endpoint, a server-side JavaScript client, and the decisions needed to turn a response into a dependable product feature. It is suitable for a resale research tool, an inventory intake screen, or the enrichment step after a keyword search.

Availability: Check capabilities before running data requests. While dataEnabled is false, authenticated lookups return 503. The examples describe the implemented contract; they do not imply that every listing or marketplace is available.

Start with a listing ID and a storefront

SoldFetch accepts an eBay item ID containing 9–15 numeric digits. Keep it as a string throughout your application. It is an identifier, so arithmetic, automatic formatting, and conversion to scientific notation are all unwanted behavior.

The lookup target also includes ebaySite. Use the site associated with the listing or search result, rather than deciding the marketplace from the user's browser language. The currently configured sites are ebay.com, ebay.co.uk, and ebay.de; the capabilities endpoint is the authority for the active deployment.

An item ID identifies a listing. Do not use it as your universal catalog identifier for every listing of the same model. Two sellers offering similar headphones have different listing records, and a title match alone does not establish that the accessories, capacity, condition, or bundle contents match.

A minimal target looks like this:

{
  "itemId": "377534750427",
  "ebaySite": "ebay.com"
}

The ID is illustrative and may no longer resolve. For a useful test, take an item ID from a recent SoldFetch search and keep its storefront alongside it. Validate the shape locally, then let the API determine whether data can be retrieved.

Make the first request from your server

Store your key in a backend environment variable named SOLDFETCH_API_KEY. Do not use a NEXT_PUBLIC_ prefix or place the key in browser JavaScript. A public key in a tutorial is a placeholder, not a credential to copy into production.

With that environment variable already set, run:

curl --get \
  'https://soldfetch.com/v1/item/377534750427' \
  --data-urlencode 'ebaySite=ebay.com' \
  --header 'Accept: application/json' \
  --header "API-KEY: $SOLDFETCH_API_KEY"

Authorization: Bearer ... is also supported. Choose one authentication convention for your client and use it consistently. Sending conflicting credentials in different headers is an input error.

The item endpoint accepts the path item ID and the ebaySite query parameter. Search controls such as keyword, count, and itemCondition do not belong on an item request. Unknown or unsupported parameters are rejected, which makes incorrect assumptions visible during integration.

Read the response as a listing observation

The response is a flat JSON object. There is no data.product wrapper to unwrap. This shortened example demonstrates field types and uncertainty; it is not a live response for the example ID:

{
  "itemId": "377534750427",
  "url": "https://www.ebay.com/itm/377534750427",
  "title": "Illustrative used headphones listing",
  "price": "89.99",
  "currency": "USD",
  "condition": "Used",
  "ended": false,
  "endedDate": null,
  "bestOfferAccepted": null,
  "shipping": null,
  "images": [],
  "itemSpecifics": {},
  "description": null,
  "scrapedAt": "2026-10-09T09:00:00Z",
  "fetchedAt": "2026-10-09T09:00:00Z",
  "warnings": [
    "An ended listing is not proof of a sale. Displayed price is not a verified accepted offer."
  ]
}

The three most consequential distinctions are price versus payment, missing versus zero, and listing state versus transaction state. price is the displayed amount. It is not independently verified as a buyer's final payment. shipping: null means the amount is unknown, not free. And ended: true says that the listing ended; it does not establish that a sale occurred.

Keep warnings with the observation, even if your main UI summarizes them more briefly. They explain why a listing should not automatically enter a verified sales-price series.

Field group How to use it
itemId, url Retain source identity and a way to inspect the listing.
price, currency Store together; amounts are decimal strings or null.
condition, conditionDescription Preserve source wording before grouping records.
ended, endedDate Show listing state without treating it as a verified transaction.
seller, itemSpecifics, categoryPath Use available context; tolerate missing values.
images Handle an empty array and unavailable images gracefully.
scrapedAt, fetchedAt Keep the observation's data time separate from your ingestion time.

Build a small client with an explicit timeout

The following Node.js helper validates the target, keeps the secret on the server, and returns the metadata needed for diagnostics. A 45-second client timeout gives the request a finite budget; it is an example setting, not a latency guarantee.

export async function lookupItem({ itemId, ebaySite }) {
  if (!/^\d{9,15}$/.test(itemId)) {
    throw new TypeError("Expected a numeric eBay item ID");
  }
  if (!process.env.SOLDFETCH_API_KEY) {
    throw new Error("SOLDFETCH_API_KEY is missing");
  }

  const url = new URL(`/v1/item/${itemId}`, "https://soldfetch.com");
  url.searchParams.set("ebaySite", ebaySite);

  const response = await fetch(url, {
    headers: {
      Accept: "application/json",
      "API-KEY": process.env.SOLDFETCH_API_KEY,
    },
    signal: AbortSignal.timeout(45_000),
  });
  const body = await response.json();
  const metadata = {
    requestId: response.headers.get("x-request-id"),
    cache: response.headers.get("x-soldfetch-cache"),
    fetchedAt: response.headers.get("x-soldfetch-data-fetched-at"),
    usageUnits: response.headers.get("x-soldfetch-usage-units"),
    retryAfter: response.headers.get("retry-after"),
  };

  if (!response.ok) {
    const error = new Error(body.error ?? `HTTP ${response.status}`);
    Object.assign(error, { status: response.status, code: body.code, metadata });
    throw error;
  }
  return { item: body, metadata };
}

Call this helper from a route handler, server action, or queue worker. Browser code should call your own authenticated application endpoint. That boundary lets you decide which users may perform lookups and how much of your request budget they may consume.

Avoid returning the upstream secret in debugging output. Usually the request ID, status, error code, and latency are enough to investigate a failure. Keep the listing payload out of generic exception logs unless you have a specific retention and access policy for it.

Preserve unknown values through normalization

It is tempting to simplify the response with defaults such as Number(item.price || 0). That turns an unavailable price into an apparently real zero-dollar observation. Once that row enters a chart or alert rule, the original uncertainty is lost.

Instead, make missing data an explicit state:

export function toListingRecord(item, ebaySite) {
  return {
    key: `${ebaySite}:${item.itemId}`,
    itemId: item.itemId,
    ebaySite,
    sourceUrl: item.url,
    title: item.title,
    amount: item.price,
    currency: item.currency,
    priceState: item.price === null ? "unknown" : "observed",
    condition: item.condition,
    ended: item.ended,
    observedAt: item.scrapedAt,
    recordedAt: new Date().toISOString(),
    warnings: item.warnings,
  };
}

For storage and calculations, use an appropriate decimal representation. Preserve the currency on every row, and do not aggregate USD, GBP, and EUR into one median. Currency conversion is a separate product decision that needs its own rate and timestamp.

Likewise, keep item details separate from search-result fields. An item response uses price; sold search rows use soldPrice; active search rows use currentPrice. A shared internal model can normalize these names, but it must retain which observation produced the value.

Handle errors according to their meaning

A failed lookup should become a fetch-attempt record, not a new listing price. Make retry behavior depend on the status and error code together.

Response Client behavior
400 invalid_parameters Correct the request before sending it again.
400 unsupported_capability Remove an unsupported parameter or select an enabled site.
401 unauthorized Check the credential; do not retry in a tight loop.
403 forbidden Review key access and workspace state.
404 item_not_found Preserve the failed observation and review the target.
429 rate_limited Respect Retry-After when present and slow the shared queue.
429 quota_exceeded Stop the sweep; repeated retries will not add allowance.
5xx Inspect the message; use bounded retries only for a temporary failure.

A deployment with data access disabled also returns 503. Treat that as unavailable service, not evidence that the item disappeared. Check capabilities before scheduling a large workload.

Use a maximum attempt count and an overall deadline. If your worker gives up, keep the last valid observation visible with its age and a failed-refresh indicator. Replacing it with a blank or zero price makes a transport problem look like a marketplace event.

Account for caching and request units

One successful item lookup consumes one request unit. A cache hit also consumes one unit; a failed request consumes zero. SoldFetch currently caches successful operation results for five minutes, so a new HTTP response does not necessarily mean the source was fetched again.

Use X-SoldFetch-Cache and X-SoldFetch-Data-Fetched-At to distinguish the response time from the source-data time. If two calls return the same underlying observation, your history store can keep one observation and two fetch-attempt records instead of drawing a misleading second data point.

For retry safety, send an Idempotency-Key with a logical request. A successful replay within the retention window returns the saved result with zero additional units. Reusing that key for a different item or storefront returns 409. A new observation requires a new key; idempotency is not a mechanism for repeatedly refreshing one listing.

Choose when item enrichment is worth a call

Start with search when the application is still discovering candidate listings. Search already provides identity, displayed price, condition, and source links for many results at once. Calling the item endpoint for every row can add cost without helping the user make a decision.

Enrich the candidates that pass your first comparison rules or that a user opens. For example, a sourcing screen might search used headphones, exclude accessories from the candidate list, and look up details only for the five listings selected for review.

Keep the enrichment timestamp separate from the original search timestamp. If the two responses disagree, show the later observation and retain the earlier evidence rather than silently overwriting an unexplained difference.

Before shipping your lookup feature

Verify a current listing, an unavailable listing, a missing price, and an empty image array. Confirm that API keys never reach the browser, all requests have a timeout, and a 429 slows your queue instead of causing a retry burst. Inspect the rendered UI with long titles and null seller details.

Finally, make the wording match the evidence. “Displayed listing price” is a defensible label. “Verified sale amount” is not a field supplied by this endpoint. This small distinction keeps the interface useful without giving a sparse response more certainty than it contains.

References and next steps

Topics

#ebay-api#item-id#listing-data#integration

Related posts

AI agent or LLM? Read this page as Markdown