eBay Hardware Hunter
An agent skill that shops eBay for hardware on a budget, scores the listings,
and records them - all through the Zapier SDK. (Build walkthrough:
https://mrc.fm/hardwarehunter.) The native eBay Zapier app
is seller-focused (orders, fulfilment), so this skill reaches the buyer-side
eBay Browse API through Zapier's authenticated curl, which injects your
connected eBay account's OAuth token. Everything stays inside the Zapier SDK.
search (eBay Browse API via Zapier curl) -> score -> log (Zapier Table) -> report best deal + backups
Prerequisites (one-time setup)
Node.js 20+ and the Zapier SDK packages:
npm install @zapier/zapier-sdk@^0.70.2 @zapier/zapier-sdk-cli@^0.55.3
Every command below is run as npx zapier-sdk <command>.
Log the SDK into Zapier (browser auth, token stored locally for the OS
user that runs it):
npx zapier-sdk login
Verify with npx zapier-sdk get-profile.
Headless box? The login prints an authorize URL and listens on
localhost:<port> for the callback. Open the URL, approve, then curl the
resulting http://localhost:<port>/oauth?code=...&state=... URL from the
same machine the CLI is running on to complete the handshake.
Connect your eBay account to Zapier at
https://zapier.com/app/connections -> Add connection -> eBay ->
sign in and authorize. This grant carries the base api_scope the Browse API
needs. Confirm it exists:
npx zapier-sdk find-first-connection ebay --json
Configuration
Read these knobs from the user's request; fall back to the defaults.
| Setting |
Default |
Notes |
QUERY |
mac mini |
eBay search keywords. |
CATEGORY_ID |
111418 |
eBay category to restrict to (111418 = Apple Desktops & All-in-Ones). Omit to search all categories. Strongly recommended - without it, accessories dominate (see relevance guard below). |
EXCLUDE |
box only, empty box, dock, hub, stand, case, shell, charger, cable, psu, power supply, lead, spares, faulty, screen protector, sticker, skin |
Title keywords that mean "not the actual machine". |
MARKETPLACE |
EBAY_GB |
eBay marketplace id (EBAY_US, EBAY_DE, ...). |
CURRENCY |
GBP |
Must match the marketplace (USD for EBAY_US, etc.). |
BUDGET |
400 |
Max total price (item) you'll consider, in CURRENCY. |
MIN_PRICE |
50 |
Floor to skip junk/parts listings. |
CONDITIONS |
`USED |
NEW` |
BUYING_OPTIONS |
FIXED_PRICE |
FIXED_PRICE, AUCTION, or `FIXED_PRICE |
MAX_RESULTS |
15 |
Listings to fetch, score, and log. |
TABLE_NAME |
Mac mini deals |
Zapier Table to log into (created if missing). |
Procedure
Step 1 - Resolve the eBay connection id
npx zapier-sdk find-first-connection ebay --json
Take .data.id. If data is null, stop and tell the user to connect eBay
(Prerequisite 3).
Step 2 - Search eBay (Browse API through Zapier)
Build the Browse API URL and call it with curl --connection <id>, passing the
marketplace header. URL-encode the query and the filter value.
npx zapier-sdk curl \
"https://api.ebay.com/buy/browse/v1/item_summary/search?q=mac%20mini&limit=15&sort=price&category_ids=111418&filter=price:%5B50..400%5D,priceCurrency:GBP,buyingOptions:%7BFIXED_PRICE%7D,conditions:%7BUSED%7CNEW%7D" \
--connection <CONNECTION_ID> \
-H "X-EBAY-C-MARKETPLACE-ID: EBAY_GB"
Notes for building the URL from config:
q= URL-encoded QUERY.
category_ids= CATEGORY_ID (omit the param entirely if CATEGORY_ID is blank).
limit= MAX_RESULTS (max 200 per page).
sort=price returns cheapest first.
filter=price:[MIN_PRICE..BUDGET],priceCurrency:CURRENCY,buyingOptions:{BUYING_OPTIONS},conditions:{CONDITIONS}
- encode
[ as %5B, ] as %5D, { as %7B, } as %7D, | as %7C.
- Header
X-EBAY-C-MARKETPLACE-ID: MARKETPLACE.
Relevance guard - do not skip this. A bare keyword search for popular
hardware is full of accessories and parts. q=mac mini alone surfaces docks,
hubs, stands, and even "BOX only" listings that are cheap and therefore score
as "great deals". Two defences, both applied: (1) CATEGORY_ID restricts the
server-side search to actual machines; (2) the EXCLUDE keyword check in
Step 3 drops anything whose title still betrays a non-machine.
The response is JSON with an itemSummaries array. For each item read:
title, price.value, shippingOptions[0].shippingCost.value (absent ->
treat as collection/unknown, default 0), condition, buyingOptions,
seller.username, seller.feedbackPercentage, seller.feedbackScore,
marketingPrice.discountPercentage (optional), itemId, and itemWebUrl (the
direct buy link).
Step 3 - Score each listing (0-100)
First drop irrelevant listings: skip any item whose lowercased title
contains an EXCLUDE keyword (box only, dock, hub, stand, case, charger,
cable, etc.). This is what stops an empty "Mac Mini BOX only" from topping the
ranking.
Then compute total = price + shipping and skip anything with total > BUDGET.
Sum the components below (clamp the final score to 0-100). State the rubric to
the user so the ranking is transparent.
- Value vs budget - up to 40.
40 * (BUDGET - total) / BUDGET. Cheaper
relative to budget scores higher; at-budget scores ~0.
- Seller trust - up to 25.
25 * (feedbackPercentage / 100), then subtract
up to 10 if feedbackScore < 50 (thin track record). Floor at 0.
- Condition - up to 15.
NEW/Brand New = 15; Certified/Excellent - Refurbished = 12; Used/Very Good/Good = 8; For parts or not working
= 0.
- Buying option - up to 10.
FIXED_PRICE (buy-it-now, instant) = 10;
FIXED_PRICE with BEST_OFFER = 10 (+ note offer is possible);
AUCTION = 5 (price not final).
- Shipping - up to 10. Free = 10; otherwise
10 * (1 - shipping / 20)
floored at 0 (i.e. >= 20 in shipping scores 0).
- Discount bonus - up to 5. If
marketingPrice.discountPercentage present,
add min(5, discountPercentage / 10).
Sort listings by score descending. The top one is the recommended deal.
Step 4 - Ensure the log table exists
npx zapier-sdk list-tables --json
Find a table whose name equals TABLE_NAME. If none exists, create it and its
fields (one-time):
npx zapier-sdk create-table "Mac mini deals" --description "eBay hardware hunter log"
# take .data.id from the output, then:
npx zapier-sdk create-table-fields <TABLE_ID> '[
{"name":"title","type":"text"},
{"name":"score","type":"number"},
{"name":"price_gbp","type":"text"},
{"name":"shipping_gbp","type":"text"},
{"name":"total_gbp","type":"text"},
{"name":"condition","type":"text"},
{"name":"buying_options","type":"text"},
{"name":"seller","type":"text"},
{"name":"seller_feedback_pct","type":"text"},
{"name":"seller_feedback_score","type":"number"},
{"name":"url","type":"text"},
{"name":"item_id","type":"text"},
{"name":"found_at","type":"text"}
]'
Money and percentage values are stored as text on purpose: Zapier Table
number fields round to integers on write, which would turn 319.99 into
319. Keep score and seller_feedback_score as number.
Step 5 - Log the scored listings
Each record must be wrapped in {"data": {...}}. Write all scored listings
(up to 100 per call):
npx zapier-sdk create-table-records <TABLE_ID> '[
{"data":{
"title":"Apple Mac mini (2020) M1 256GB 8GB",
"score":82,
"price_gbp":"319.99",
"shipping_gbp":"0.00",
"total_gbp":"319.99",
"condition":"Excellent - Refurbished",
"buying_options":"FIXED_PRICE",
"seller":"clove-technology",
"seller_feedback_pct":"99.1",
"seller_feedback_score":15282,
"url":"https://www.ebay.co.uk/itm/267581736217",
"item_id":"v1|267581736217|0",
"found_at":"2026-06-15T16:45:00Z"
}}
]'
Use the item's itemWebUrl for url. Set found_at to the current UTC time.
Step 6 - Report to the user
Reply in chat with:
- Best deal: title, total price, condition, seller (feedback %), buy link,
and one line on why it won (the scoring breakdown).
- 2-3 backups: title, total, link.
- A line confirming how many listings were logged to the
TABLE_NAME table.
This chat report is the "notification" (this skill is notification-channel
agnostic; see Extending below to fan out to Slack, etc.).
Failure modes & fallbacks
find-first-connection ebay returns null -> eBay isn't connected. Send
the user to https://zapier.com/app/connections. Do not proceed.
- Browse API returns
errorId 1100/"Access denied" or HTTP 403 -> the
connected eBay token lacks Browse scope. Reconnect eBay in Zapier; if it still
fails, the account may need a fresh OAuth grant. (In testing, a standard eBay
connection carries the base api_scope the Browse search needs.)
"total": 0 / empty itemSummaries -> loosen the filter: widen BUDGET,
add AUCTION to BUYING_OPTIONS, or broaden CONDITIONS. Report the empty
result honestly rather than inventing listings.
- Auth error on any
zapier-sdk call -> run npx zapier-sdk login as the
OS user that will run the skill (the token is per-user).
Known limitations
- The Browse API item summary does not expose watcher counts or bid counts,
so those are not scored or logged.
- This skill finds and records deals and hands you a purchase link - it does
not place bids or buy. eBay's API and the Zapier eBay app do not expose
buyer bidding/checkout, so completing a purchase stays a human click. (This
matches the "prepare checkout / deep link" approach.)
Extending
1---2name: ebay-hardware-hunter3description: Hunt eBay for a target piece of hardware (default: Mac mini) on a budget, score every listing for deal quality, log them to a Zapier Table, and report the best buy plus backups with direct purchase links. Built entirely on the Zapier SDK CLI - no eBay developer keys, no app code. Use when the user wants to find, compare, or track hardware deals on eBay (e.g. "find me a cheap Mac mini", "hunt for compute", "what RAM upgrades are going on eBay", "log today's GPU deals").4license: MIT5---67# eBay Hardware Hunter89An agent skill that shops eBay for hardware on a budget, scores the listings,10and records them - all through the **Zapier SDK**. (Build walkthrough:11<https://mrc.fm/hardwarehunter>.) The native eBay Zapier app12is seller-focused (orders, fulfilment), so this skill reaches the **buyer-side13eBay Browse API** through Zapier's authenticated `curl`, which injects your14connected eBay account's OAuth token. Everything stays inside the Zapier SDK.1516```17search (eBay Browse API via Zapier curl) -> score -> log (Zapier Table) -> report best deal + backups18```1920## Prerequisites (one-time setup)21221. **Node.js 20+** and the Zapier SDK packages:23 ```bash24 npm install @zapier/zapier-sdk@^0.70.2 @zapier/zapier-sdk-cli@^0.55.325 ```26 Every command below is run as `npx zapier-sdk <command>`.27282. **Log the SDK into Zapier** (browser auth, token stored locally for the OS29 user that runs it):30 ```bash31 npx zapier-sdk login32 ```33 Verify with `npx zapier-sdk get-profile`.34 > Headless box? The login prints an authorize URL and listens on35 > `localhost:<port>` for the callback. Open the URL, approve, then `curl` the36 > resulting `http://localhost:<port>/oauth?code=...&state=...` URL **from the37 > same machine** the CLI is running on to complete the handshake.38393. **Connect your eBay account to Zapier** at40 <https://zapier.com/app/connections> -> **Add connection** -> **eBay** ->41 sign in and authorize. This grant carries the base `api_scope` the Browse API42 needs. Confirm it exists:43 ```bash44 npx zapier-sdk find-first-connection ebay --json45 ```4647## Configuration4849Read these knobs from the user's request; fall back to the defaults.5051| Setting | Default | Notes |52|------------------|----------------|--------------------------------------------------------------|53| `QUERY` | `mac mini` | eBay search keywords. |54| `CATEGORY_ID` | `111418` | eBay category to restrict to (`111418` = Apple Desktops & All-in-Ones). Omit to search all categories. **Strongly recommended** - without it, accessories dominate (see relevance guard below). |55| `EXCLUDE` | `box only, empty box, dock, hub, stand, case, shell, charger, cable, psu, power supply, lead, spares, faulty, screen protector, sticker, skin` | Title keywords that mean "not the actual machine". |56| `MARKETPLACE` | `EBAY_GB` | eBay marketplace id (`EBAY_US`, `EBAY_DE`, ...). |57| `CURRENCY` | `GBP` | Must match the marketplace (`USD` for `EBAY_US`, etc.). |58| `BUDGET` | `400` | Max total price (item) you'll consider, in `CURRENCY`. |59| `MIN_PRICE` | `50` | Floor to skip junk/parts listings. |60| `CONDITIONS` | `USED|NEW` | eBay condition set: `NEW`, `USED`, `CERTIFIED_REFURBISHED`. |61| `BUYING_OPTIONS` | `FIXED_PRICE` | `FIXED_PRICE`, `AUCTION`, or `FIXED_PRICE|AUCTION`. |62| `MAX_RESULTS` | `15` | Listings to fetch, score, and log. |63| `TABLE_NAME` | `Mac mini deals` | Zapier Table to log into (created if missing). |6465## Procedure6667### Step 1 - Resolve the eBay connection id68```bash69npx zapier-sdk find-first-connection ebay --json70```71Take `.data.id`. If `data` is null, stop and tell the user to connect eBay72(Prerequisite 3).7374### Step 2 - Search eBay (Browse API through Zapier)75Build the Browse API URL and call it with `curl --connection <id>`, passing the76marketplace header. URL-encode the query and the `filter` value.7778```bash79npx zapier-sdk curl \80 "https://api.ebay.com/buy/browse/v1/item_summary/search?q=mac%20mini&limit=15&sort=price&category_ids=111418&filter=price:%5B50..400%5D,priceCurrency:GBP,buyingOptions:%7BFIXED_PRICE%7D,conditions:%7BUSED%7CNEW%7D" \81 --connection <CONNECTION_ID> \82 -H "X-EBAY-C-MARKETPLACE-ID: EBAY_GB"83```8485Notes for building the URL from config:86- `q=` URL-encoded `QUERY`.87- `category_ids=` `CATEGORY_ID` (omit the param entirely if `CATEGORY_ID` is blank).88- `limit=` `MAX_RESULTS` (max 200 per page).89- `sort=price` returns cheapest first.90- `filter=price:[MIN_PRICE..BUDGET],priceCurrency:CURRENCY,buyingOptions:{BUYING_OPTIONS},conditions:{CONDITIONS}`91 - encode `[` as `%5B`, `]` as `%5D`, `{` as `%7B`, `}` as `%7D`, `|` as `%7C`.92- Header `X-EBAY-C-MARKETPLACE-ID: MARKETPLACE`.9394> **Relevance guard - do not skip this.** A bare keyword search for popular95> hardware is full of accessories and parts. `q=mac mini` alone surfaces docks,96> hubs, stands, and even "BOX only" listings that are cheap and therefore score97> as "great deals". Two defences, both applied: (1) `CATEGORY_ID` restricts the98> server-side search to actual machines; (2) the `EXCLUDE` keyword check in99> Step 3 drops anything whose title still betrays a non-machine.100101The response is JSON with an `itemSummaries` array. For each item read:102`title`, `price.value`, `shippingOptions[0].shippingCost.value` (absent ->103treat as collection/unknown, default 0), `condition`, `buyingOptions`,104`seller.username`, `seller.feedbackPercentage`, `seller.feedbackScore`,105`marketingPrice.discountPercentage` (optional), `itemId`, and `itemWebUrl` (the106direct buy link).107108### Step 3 - Score each listing (0-100)109First **drop irrelevant listings**: skip any item whose lowercased `title`110contains an `EXCLUDE` keyword (box only, dock, hub, stand, case, charger,111cable, etc.). This is what stops an empty "Mac Mini BOX only" from topping the112ranking.113114Then compute `total = price + shipping` and skip anything with `total > BUDGET`.115Sum the components below (clamp the final score to 0-100). State the rubric to116the user so the ranking is transparent.117118- **Value vs budget - up to 40.** `40 * (BUDGET - total) / BUDGET`. Cheaper119 relative to budget scores higher; at-budget scores ~0.120- **Seller trust - up to 25.** `25 * (feedbackPercentage / 100)`, then subtract121 up to 10 if `feedbackScore < 50` (thin track record). Floor at 0.122- **Condition - up to 15.** `NEW`/`Brand New` = 15; `Certified`/`Excellent -123 Refurbished` = 12; `Used`/`Very Good`/`Good` = 8; `For parts or not working`124 = 0.125- **Buying option - up to 10.** `FIXED_PRICE` (buy-it-now, instant) = 10;126 `FIXED_PRICE` **with** `BEST_OFFER` = 10 (+ note offer is possible);127 `AUCTION` = 5 (price not final).128- **Shipping - up to 10.** Free = 10; otherwise `10 * (1 - shipping / 20)`129 floored at 0 (i.e. >= 20 in shipping scores 0).130- **Discount bonus - up to 5.** If `marketingPrice.discountPercentage` present,131 add `min(5, discountPercentage / 10)`.132133Sort listings by score descending. The top one is the recommended deal.134135### Step 4 - Ensure the log table exists136```bash137npx zapier-sdk list-tables --json138```139Find a table whose `name` equals `TABLE_NAME`. If none exists, create it and its140fields (one-time):141```bash142npx zapier-sdk create-table "Mac mini deals" --description "eBay hardware hunter log"143# take .data.id from the output, then:144npx zapier-sdk create-table-fields <TABLE_ID> '[145 {"name":"title","type":"text"},146 {"name":"score","type":"number"},147 {"name":"price_gbp","type":"text"},148 {"name":"shipping_gbp","type":"text"},149 {"name":"total_gbp","type":"text"},150 {"name":"condition","type":"text"},151 {"name":"buying_options","type":"text"},152 {"name":"seller","type":"text"},153 {"name":"seller_feedback_pct","type":"text"},154 {"name":"seller_feedback_score","type":"number"},155 {"name":"url","type":"text"},156 {"name":"item_id","type":"text"},157 {"name":"found_at","type":"text"}158]'159```160> Money and percentage values are stored as **text** on purpose: Zapier Table161> `number` fields round to integers on write, which would turn `319.99` into162> `319`. Keep `score` and `seller_feedback_score` as `number`.163164### Step 5 - Log the scored listings165Each record must be wrapped in `{"data": {...}}`. Write all scored listings166(up to 100 per call):167```bash168npx zapier-sdk create-table-records <TABLE_ID> '[169 {"data":{170 "title":"Apple Mac mini (2020) M1 256GB 8GB",171 "score":82,172 "price_gbp":"319.99",173 "shipping_gbp":"0.00",174 "total_gbp":"319.99",175 "condition":"Excellent - Refurbished",176 "buying_options":"FIXED_PRICE",177 "seller":"clove-technology",178 "seller_feedback_pct":"99.1",179 "seller_feedback_score":15282,180 "url":"https://www.ebay.co.uk/itm/267581736217",181 "item_id":"v1|267581736217|0",182 "found_at":"2026-06-15T16:45:00Z"183 }}184]'185```186Use the item's `itemWebUrl` for `url`. Set `found_at` to the current UTC time.187188### Step 6 - Report to the user189Reply in chat with:190- **Best deal**: title, total price, condition, seller (feedback %), buy link,191 and one line on why it won (the scoring breakdown).192- **2-3 backups**: title, total, link.193- A line confirming how many listings were logged to the `TABLE_NAME` table.194195This chat report is the "notification" (this skill is notification-channel196agnostic; see Extending below to fan out to Slack, etc.).197198## Failure modes & fallbacks199200- **`find-first-connection ebay` returns null** -> eBay isn't connected. Send201 the user to <https://zapier.com/app/connections>. Do not proceed.202- **Browse API returns `errorId` 1100/"Access denied" or HTTP 403** -> the203 connected eBay token lacks Browse scope. Reconnect eBay in Zapier; if it still204 fails, the account may need a fresh OAuth grant. (In testing, a standard eBay205 connection carries the base `api_scope` the Browse search needs.)206- **`"total": 0` / empty `itemSummaries`** -> loosen the filter: widen `BUDGET`,207 add `AUCTION` to `BUYING_OPTIONS`, or broaden `CONDITIONS`. Report the empty208 result honestly rather than inventing listings.209- **Auth error on any `zapier-sdk` call** -> run `npx zapier-sdk login` as the210 OS user that will run the skill (the token is per-user).211212## Known limitations213214- The Browse API item summary does **not** expose watcher counts or bid counts,215 so those are not scored or logged.216- This skill **finds and records** deals and hands you a purchase link - it does217 **not** place bids or buy. eBay's API and the Zapier eBay app do not expose218 buyer bidding/checkout, so completing a purchase stays a human click. (This219 matches the "prepare checkout / deep link" approach.)220221## Extending222223- **Other hardware**: change `QUERY` (e.g. `rtx 4090`, `ddr5 64gb`, `thinkpad224 x1`). Tune `BUDGET`/`CONDITIONS` to match.225- **Other notification channels**: any Zapier-connected app can be the alert.226 After Step 6, e.g. Slack:227 ```bash228 npx zapier-sdk run-action slack write create_message \229 --inputs '{"channel":"#deals","text":"Best Mac mini lead: ..."}'230 ```231 (Requires a Slack connection. Discover inputs with232 `npx zapier-sdk get-action-input-fields-schema slack write create_message`.)233- **Scheduling**: run this skill on a cron to hunt daily and keep the table as a234 running price history.235236<!-- SPDX-License-Identifier: MIT -->237<!-- Licensed under the MIT License. -->238<!-- Attribution: Mike Russell (mike-russell) -->