Back to blog

Moving an eBay Integration to SoldFetch

By SoldFetch Engineering10 min read

Plan an eBay API migration with familiar endpoint paths, explicit capability checks, response mapping, quota accounting, and a staged rollout.

Moving an eBay Integration to SoldFetch

An API migration succeeds when the application keeps answering the same user question after the provider changes. Familiar URLs help, but they are only one part of the contract. Authentication, field types, null handling, pagination, errors, and request accounting all affect what your customer sees.

SoldFetch implements familiar eBay paths including /v1/scrape, /v1/scrape/category, and /v1/item/{itemId}. This guide describes how to evaluate those paths for an existing integration, including one built around SoldComps. It documents SoldFetch's behavior rather than claiming that every feature of another service is interchangeable.

The practical approach is to put a small adapter between your application and the provider, test it against representative fixtures, and move traffic gradually. Keep the original data source available until the new integration meets your acceptance criteria.

Inventory the behavior your application depends on

Start with the calls your application actually makes. Export endpoint names, parameter names, response fields consumed by the UI, retry rules, and expected error codes. Do not begin with a broad list of every feature a provider advertises; begin with the paths that your users rely on.

For each workflow, write a short acceptance statement. A sourcing tool might require “return candidate sold listings with item identity, condition, currency, and displayed price.” An accounting workflow might require “return a verified transaction amount on an exact sale date.” These are different requirements, and the latter is not a capability this SoldFetch release provides.

Include secondary consumers in the inventory: CSV exports, background jobs, alert rules, analytics pipelines, and support tools. A renamed nullable field can break a report even if the main dashboard continues to render.

Separate essential behavior from optional enrichment. That makes it possible to approve a narrow migration without accidentally committing to features the new integration cannot fulfill.

Check live capabilities before switching a base URL

The capabilities endpoint is public and does not require an API key:

curl --fail --silent --show-error \
  'https://soldfetch.com/v1/capabilities'

Read dataEnabled, enabledSites, limits, quality, and unsupported. The initial configured marketplaces are the US, UK, and Germany, but clients should consult the deployed capability response rather than assume every syntactically valid eBay site is enabled.

When dataEnabled is false, authenticated customer data calls return 503. A working documentation page or a successful capabilities response is not evidence that live collection is available. Treat this check as a release prerequisite before sending evaluation traffic.

The contract exposes three operation types: search, category, and product. Category-only sold requests can encounter an upstream sign-in restriction and return an uncharged upstream_blocked response. If category-only collection is essential to your application, resolve that requirement before cutting over.

Map the endpoints and workflow boundaries

The table below is a map of SoldFetch's implemented routes. Match each one to the actual behavior required by your existing integration, rather than assuming a shared path guarantees an identical payload.

Workflow SoldFetch route Integration boundary
Sold or active keyword search GET /v1/scrape Requires keyword; sold=true by default.
Category search GET /v1/scrape/category Requires a nonzero categoryId; upstream availability can differ.
Listing details GET /v1/item/{itemId} Numeric item ID plus ebaySite; flat item response.
Start bounded search POST /v1/scrape/max Creates an asynchronous job with a page budget.
Read job results GET /v1/scrape/max/results/{jobId} Owned job, bounded retention, explicit state.
Read or cancel a job GET or DELETE /v1/scrape/max/{jobId} Cancellation does not reverse completed usage.
Download collected rows GET /v1/scrape/max/{jobId}/download.csv Authenticated download of retained results.
Bulk keyword search POST /api/bulk-search Server-sent events; at most 20 keywords.
Account accounting GET /usage, GET /requests Authenticate with the workspace's SoldFetch key.

Use the OpenAPI document and API reference for request schemas. Keep the adapter small enough that a reviewer can see where each outgoing field comes from.

Replace authentication explicitly

A credential from another provider cannot authenticate to SoldFetch. Create a SoldFetch workspace key and save it in your server environment. The core REST routes accept API-KEY or an Authorization: Bearer header.

curl --get 'https://soldfetch.com/v1/scrape' \
  --data-urlencode 'keyword=sony wh-1000xm5' \
  --data-urlencode 'ebaySite=ebay.com' \
  --data-urlencode 'sold=true' \
  --data-urlencode 'count=60' \
  --header "API-KEY: $SOLDFETCH_API_KEY"

The RapidAPI gateway authentication flow is not supported. Do not carry gateway headers into your new client and assume they will authorize the request. Keep provider credentials separate during a comparison run so one configuration change cannot send the wrong key to the wrong destination.

For client applications, keep the secret behind your own server endpoint. Your application still needs its own authorization and abuse controls; the upstream key should not be distributed to end-user browsers.

Translate parameters without silently dropping them

Search uses keyword, ebaySite, page, count, and the supported filters documented in the reference. The broad condition parameter is itemCondition, with any, new, or used. Boolean query parameters use the literal strings true and false.

Unknown parameters and unsupported capabilities return explicit errors. This behavior is useful during migration: a forgotten filter cannot quietly disappear while your application presents the resulting sample as equivalent.

The following requests need a product decision rather than a mechanical rename:

  • Exact sale-date windows using soldAfter or soldBefore are unavailable.
  • Accepted-offer enrichment with hydrateBoa is unavailable.
  • Numeric conditionId, sellerType, and aspectFilter filters are unavailable.
  • Non-default itemLocation and nearest-distance sorting are unavailable.
  • Item descriptions, emailed exports, and date-window jobs are unavailable.

Do not substitute a keyword heuristic for one of these filters without telling the rest of your application. A title-based approximation has different semantics from a verified structured filter. If an approximation is acceptable, give it an explicit policy name and test it separately.

The exactMatch behavior also needs care. Filtering supplier-flagged mismatches is not independent verification that every returned listing is the exact product your user intended. The quality metadata continues to mark exact product matching as unverified.

Normalize payloads at your application boundary

SoldFetch's core REST responses are flat JSON. Search returns an items array and page metadata. Item details return one listing object. Preserve the original payload shape in fixtures, then normalize only the fields your application needs.

For example, a UI that displays candidate prices can use this small adapter for search rows:

export function toCandidate(row, ebaySite) {
  const isSold = row.listingType === "sold";
  return {
    id: `${ebaySite}:${row.itemId}`,
    itemId: row.itemId,
    marketplace: ebaySite,
    url: row.url,
    title: row.title,
    price: isSold ? row.soldPrice : row.currentPrice,
    currency: isSold ? row.soldCurrency : row.currentCurrency,
    shipping: row.shippingPrice,
    shippingCurrency: row.shippingCurrency,
    total: row.totalPrice,
    condition: row.condition,
    observedAt: row.scrapedAt,
    supplierSaleDate: isSold ? row.endedAt : null,
    quality: row.quality,
  };
}

Keep decimal amounts as decimal strings or a decimal type until your application deliberately performs arithmetic. Preserve nulls for unknown amounts. If an older adapter fills missing shipping with zero, change that rule before comparing outputs: it can make two providers appear to disagree when your default is the source of the difference.

Do not convert a null bestOfferAccepted field to false. Unknown is a third state. Similarly, an ended item is not proof of a completed sale. Map these distinctions through exports, chart tooltips, and filters rather than retaining them only in an internal debug view.

Verify request accounting and retry semantics

A successful search page costs one request unit, whether it returns one row or the maximum allowed count. A successful item lookup also costs one. Cache hits and successful empty pages consume one unit; failed requests consume zero.

A successful replay using the same Idempotency-Key costs zero additional units within the 24-hour retention window. Reusing that key with different normalized parameters returns 409. An in-progress or previously failed request can also return 409 with a message explaining the state.

Test these cases with a small controlled budget before connecting your scheduler:

Test Expected client decision
Successful request Record response units and the request ID.
Completed idempotent replay Accept the saved result without recording a second charged unit.
Same key, changed parameters Fix the client key assignment; do not treat the conflict as a provider outage.
429 rate_limited Respect Retry-After when present and reduce request pressure.
429 quota_exceeded Stop work until allowance is available.
Temporary upstream error Retry within a bounded budget; retain failure evidence.

The default workspace limits are 60 data requests per minute and five concurrent requests. Multiple API keys in the same workspace do not represent independent request budgets. Coordinate web traffic, queue workers, and evaluation scripts accordingly.

Treat pagination and jobs as bounded collection

A single request returns up to 200 results. The provider can return fewer, and SoldFetch may filter or deduplicate rows. totalResults is a reported total represented as a string or null; it is not a guarantee that a collector can enumerate every matching listing.

Stop a synchronous collector at its page budget, on an empty page, or when no new item IDs appear. Retain the reason it stopped. Repeated pages should not increase the apparent sample size or produce a loop that keeps consuming requests.

For the asynchronous route, specify a deliberate maxPages instead of inheriting a large default:

curl 'https://soldfetch.com/v1/scrape/max' \
  --header 'Content-Type: application/json' \
  --header "API-KEY: $SOLDFETCH_API_KEY" \
  --data '{
    "keyword": "sony wh-1000xm5",
    "ebaySite": "ebay.com",
    "itemCondition": "used",
    "count": 60,
    "maxPages": 3,
    "resultType": "inline"
  }'

A successful creation returns 202 and a job ID; it does not mean the collection is complete. Poll with the owning workspace key and handle queued, running, complete, failed, cancelled, and expired outcomes. Retained results expire after 24 hours. The implementation allows one active search job per workspace and at most 100 pages per job.

An emailed export is not a substitute for an authenticated download in this release. If your product promises email delivery, implement and verify that workflow in your own application or keep the feature unavailable during migration.

Run a representative comparison before cutover

Choose queries from real user workflows: a common model, an ambiguous product name, a sparse result set, multiple conditions, and each marketplace you intend to support. Include one unsupported request so your UI's failure path is exercised deliberately.

Compare identity, inclusion decisions, currency, condition, missing values, fetch age, and duplicate rate before comparing a summary price. Two search snapshots taken at different times may contain different listings. Treat that as a reason to examine the evidence, not immediate proof that one integration is wrong.

Record latency distributions from your own workload. One quick response does not establish a service-level expectation, and a cached response has different acquisition work from an uncached one. Capture status and cache metadata alongside timing.

Define acceptance criteria before reviewing results. For example: no secret exposure, all unsupported filters surfaced clearly, no cross-currency totals, bounded pagination, and usage reconciliation for every evaluation call. Data quality thresholds should come from the application's actual tolerance for mismatches.

Roll out with a reversible provider choice

Route a small, deliberate slice of traffic through the new adapter first. Keep the provider choice in configuration, retain a known working rollback path, and compare error rates, response age, units, and user-visible results during the rollout.

Do not combine the provider cutover with a change to your matching algorithm. If both change simultaneously, a shift in the output becomes harder to explain. First preserve the application's interpretation; then introduce improved matching as a separate change with its own fixtures.

A completed migration should have an owner, a rollback condition, and a record of unsupported behavior. The shared endpoint names reduce integration work, while explicit boundaries keep the product from promising more than the data can support.

References and next steps

Topics

#ebay-api#api-migration#sold-listings#compatibility

Related posts

AI agent or LLM? Read this page as Markdown