An inventory intake spreadsheet may contain a camera body, a lens, and a pair of headphones. Each needs a separate research query, and some queries need more than one search page. Running an unbounded crawl makes cost, retries, and partial results difficult to explain.
SoldFetch background search jobs collect a bounded set of pages for one keyword. A client can start a job, poll its state, cancel it, and download its current CSV. This guide applies that lifecycle to a short inventory list, processing keywords sequentially and saving enough metadata to resume deliberately.
1. Distinguish a keyword list from a paginated job
SoldFetch has two different batch workflows. POST /v1/scrape/max creates a durable background job for one keyword, bounded by maxPages. POST /api/bulk-search processes up to 20 keywords on an open Server-Sent Events response, performing a search-page request for each keyword. The stream is not a durable job queue.
For multi-page inventory research, create one background job per keyword. The current implementation allows one active queued or running search job per workspace; parallel job starts can return 409. Sequential processing avoids that conflict and makes a per-keyword budget easy to understand.
Begin with precise inventory queries, not entire categories:
sony a6400 body
sony e 35mm f1.8 oss lens
sony wh-1000xm5 headphones
These are research candidates, not verified catalog matches. A returned title can still describe a case, a broken unit, a kit, or another variant. Keep each query and storefront with its exported rows.
2. Set page and time budgets before starting
Check capabilities for dataEnabled, enabled sites, page limits, and plan-specific limits. The launch markets are US, UK, and Germany. This example uses ebay.com, used condition, 60 results per page, and a maximum of three pages per keyword.
Each successful fetched page consumes one data unit. Three queries capped at three pages therefore permit at most nine successful page units. Starting, polling, cancelling, and downloading do not add data units, although authenticated job routes enforce account rate limits. A returned page may contain fewer usable or unique rows than requested.
maxPages accepts 1–100 and defaults to 50. Set it explicitly: the default is too broad for many first experiments. Set count explicitly within 1–200 as well. Workers stop when no next page is reported, no new IDs are added, or the page cap is reached.
Use a local polling deadline separately from the server page cap. A ten-minute client deadline does not mean the job is guaranteed to finish in ten minutes; worker scheduling and upstream failures affect completion.
3. Start an authenticated job and save its identity
Create a key in the dashboard and keep it in SOLDFETCH_API_KEY. Send one credential header on every authenticated job request. A start request looks like this:
POST /v1/scrape/max
API-KEY: <your key>
Content-Type: application/json
Idempotency-Key: <stable key for this exact start attempt>
{
"keyword": "sony a6400 body",
"ebaySite": "ebay.com",
"sold": true,
"itemCondition": "used",
"count": 60,
"maxPages": 3,
"resultType": "download"
}
The response is HTTP 202 with jobId, status, a relative resultsUrl, and estimatedSeconds: null. Save the ID immediately. resultType: "download" does not push a file to your computer; you still call the authenticated download endpoint.
Persist the idempotency key before the POST so an uncertain start response can be retried deliberately with the same request identity. Reusing it for changed inputs is incorrect. Job retention and idempotency are finite; neither provides permanent archival storage.
Verified date windows and emailed jobs are unavailable. Do not add daysToScrape, soldAfter, or soldBefore to claim a verified sales period, and do not request resultType: "email".
4. Poll, cancel, and handle partial work
Poll GET /v1/scrape/max/results/{jobId} with your key. The result includes status, items, totalItems, pagesCompleted, maxPages, units, error, and expiresAt. The states are queued, running, complete, failed, and cancelled.
Wait between polls. The companion client uses ten seconds as a conservative starting interval; increase it if your workspace also has other traffic. Polling too quickly can hit account limits even though it does not consume data units.
At the local deadline, send DELETE /v1/scrape/max/{jobId}. Cancellation applies to queued or running work and can race an in-flight page. Already successful requests remain billable, and accumulated results remain available until expiry. A cancellation response does not promise that no page work happened.
On failure, retain the error and partial metadata. Avoid automatically starting a replacement: the error may be authentication, quota, availability, or an unsupported capability that another job would repeat. On an uncertain network response, use the saved job ID to inspect the existing job before making a new one.
5. Run the bounded inventory client
Download the complete Python client. It uses the Python standard library and makes no requests until you run it. Save the inventory queries above in inventory-queries.txt, set your API key in the environment, and run:
python3 ebay-bulk-search-csv-background-jobs.py inventory-queries.txt \
--output inventory-research \
--site ebay.com --max-pages 3 --count 60 --wait-seconds 600
The client handles each query sequentially. For each it writes a JSON manifest before starting, saves the job ID, polls until a terminal state, and downloads CSV only after complete. The main lifecycle is:
result = api("GET", "/v1/scrape/max/results/" + job_id)
if result["status"] == "complete":
content = api("GET", "/v1/scrape/max/" + job_id + "/download.csv", csv=True)
csv_path.write_bytes(content)
elif result["status"] in {"failed", "cancelled"}:
raise RuntimeError("Inspect the saved manifest before continuing")
The complete file adds the page and time bounds, ten-second spacing, local manifests, cancellation on timeout or keyboard interruption, and a 30-second network timeout. It rejects redirects instead of forwarding the credential to an unexpected destination.
Keep the output folder when resuming. Running the same inputs with that folder reuses saved IDs; changing a query, page budget, or storefront requires a new folder. If cancellation fails, the manifest records the cancellation error so you can inspect the existing job. The client does not silently replace it.
6. Download before expiry and preserve context
The download endpoint is GET /v1/scrape/max/{jobId}/download.csv. It returns the rows accumulated so far, so downloading while a job is running produces a partial snapshot. The companion waits for completion to avoid confusing that snapshot with its final job output.
The built-in CSV columns are itemId, title, url, soldPrice, soldCurrency, shippingPrice, condition, and endedAt. The server quotes cells and neutralizes formula-like external text. Preserve those protections when transforming the file for Excel or Google Sheets.
Store the JSON manifest beside the CSV because the built-in export does not include every quality field, warning, or shipping-currency field. It retains the original query, site, page budget, last full result, units, and expiry. The row-level result JSON is the richer record for downstream validation.
Jobs currently expire 24 hours after creation. Respect the returned expiresAt; it is not 24 hours after completion. Expired reads can return 410 before cleanup, and a removed job can return 404 afterward. Download promptly and store your research artifacts locally. Revoking the job's API key can also prevent pending worker progress.
7. Use the SSE endpoint for a different batch shape
For one page per query with immediate progress, the streaming endpoint can be a better fit:
{
"keywords": ["sony a6400 body", "sony e 35mm f1.8 oss lens"],
"ebaySite": "ebay.com",
"sold": true,
"count": 60,
"page": 1
}
Send this to POST /api/bulk-search with API-key authentication. Its response contains progress, result, error, and complete events. Each result corresponds to one keyword search page, and the final event reports completed, total, and partial.
This endpoint does not return a durable jobId, does not use maxPages, and does not provide a later CSV download. Persist the result events in your own client. A disconnect stops further streaming work, with an in-flight request still possible; it does not schedule the remaining keywords for later. Certain account and availability errors stop the stream, so inspect partial before claiming the full inventory was researched. See the bulk-search guide.
8. Review the sample before using it for pricing
A completed job means the bounded collection finished. It does not mean every returned listing matches your inventory, every result page represents a distinct sale, or the sample covers the entire market. Review condition, model variants, bundles, and currencies before calculating a median.
Displayed prices are not verified accepted-offer amounts. Reported sale dates remain unverified, and exact product identity requires application-side review. Avoid turning endedAt into a verified recent-sales filter or combining all inventory queries into one price statistic.
The offline lifecycle checks cover successful completion, failed and cancelled jobs, timeouts, manifest persistence before start, resuming without another POST, changed-input rejection, and foreign-path rejection. Run them with Python before adding your key. They consume no provider credits; live availability and worker timing still require your own bounded trial.
Continue with the background-search reference or load a reviewed output into the Google Sheets connector workflow.
Get your SoldFetch API key and begin with one inventory query, three pages, and a saved manifest.



