Somebody on eBay right now is selling a pair of “Senheiser” headphones. Or a “Vitamx” blender. Or a “Le Cruset” dutch oven listed in the wrong category with one blurry photo, ending at 4:10 AM on a Tuesday. eBay search is good, but it is not perfect: a listing that most buyers never see gets few or zero bids, and closes far below what the same item sells for with a correct title.

That gap is the trade. You find the listings nobody else is looking at, bid once in the last seconds, then relist the item with a proper title, clean photos and the right category at the normal market price. Nothing about this breaks a law or an eBay rule: eBay explicitly allows last-second bidding (“sniping”), and you are simply a better searcher than the other buyers.

The manual version of this is old. Sites like FatFingers have offered typo search for years. The problem with doing it by hand is that it does not scale: you cannot check 40 brands x 20 misspellings x every few hours. A Python script on the official eBay Browse API can, and it can also do the math for you and tell you the exact maximum bid that still leaves you a profit.

This guide builds that scanner, then covers the money, the costs, and the risks. There are real risks, and the biggest one (fakes) is not the one most people expect.

How the money works

Each flip has four numbers:

  1. Buy price: the winning bid plus inbound shipping, plus sales tax (eBay collects it on your purchase in every US state that has a sales tax, unless you have a resale certificate on file with eBay).
  2. Resale price: what the same item in the same condition actually sold for recently. Sold prices, not asking prices. This guide assumes you list with free shipping, so the resale price is the whole amount the buyer pays before tax.
  3. Selling costs: eBay final value fee (around 13.6% of the total sale amount plus $0.40 per order over $10 for most US categories at time of writing; some categories differ, check eBay’s fee page for yours) plus outbound shipping and packaging. Note that “total sale amount” includes the sales tax your buyer pays, so the effective fee is a bit above 13.6% of your price.
  4. Your margin: what is left. This guide uses a floor of $40 per flip, because below that the time spent on photos, packing and returns eats you alive.

The scanner turns that into a single number per listing:

net     = resale - resale x (1 + tax) x fee_rate - 0.40 - ship_out
max_bid = (net - min_profit) / (1 + tax) - ship_in

If the current bid is below max_bid and the auction ends soon, you get an alert. You snipe at max_bid and not a cent more. If you lose, someone else overpaid. That discipline is the whole business.

Worked example (illustrative numbers, 8% sales tax): a misspelled pair of studio headphones that sells for about $300 used. Fee base is $300 + $24 tax = $324, so fees are $44.06 + $0.40 = $44.46. Outbound shipping $14. Net from resale: $241.54. Minimum profit $40 leaves $201.54 to spend including tax, which is $186.61 before tax; minus $12 inbound shipping, max_bid = $174.61. If the typo auction closes at $140, you pay ($140 + $12) x 1.08 = $164.16 and keep about $77 on one item, before packing materials and any return.

What you need

ItemCostNotes
eBay Developers Program accountFreedeveloper.ebay.com, create a Production keyset (App ID + Cert ID)
eBay buyer + seller accountFreeOlder account with feedback is better; new sellers get selling limits and payout holds
VPS or always-on PC$0 to $6/monthAny 1 GB Linux box, or a Raspberry Pi at home
Telegram botFreeCreate with @BotFather for alerts
Sniping serviceFree tierGixen is free (ad-supported, optional paid “Mirror” upgrade); or just place your max bid manually near the end
Python 3.10+Freepip install requests (in a venv)
Working capital$1,000 to $3,000 to startMoney is tied up 1 to 3 weeks per item between buying and getting paid
Packing supplies, scaleabout $50 to $80Boxes, bubble wrap, tape, a $20 shipping scale

Where does this work? Anywhere eBay is big: US, UK, Germany, Australia. The Browse API takes a marketplace ID (EBAY_US, EBAY_GB, EBAY_DE, EBAY_AU). If you live outside those markets (Indonesia, for example), the inbound and outbound shipping math usually kills the margin; you would need a local address or a partner who handles the physical side.

Step 1: Get your API keys

  1. Sign in at developer.ebay.com with your eBay account and join the Developers Program.
  2. Go to Application Keys, create a Production keyset. You get an App ID (Client ID) and Cert ID (Client Secret).
  3. eBay asks production apps to handle Marketplace Account Deletion notifications, or opt out. A read-only scanner that stores no user data can apply for the exemption in the keyset settings.

A note on access: eBay’s docs describe the Buy APIs as restricted in production, but the basic item_summary/search call used here works with a standard production keyset and the public api_scope (eBay developer support has confirmed most Browse methods are open). Bidding through the API (Offer API) is not open, which is why bids go through Gixen or your own hands. If your search calls return 403, open a ticket with eBay developer support.

Test the keys with a client credentials token (the Browse API only needs the public scope):

export EBAY_APP_ID="YourApp-xxxx-PRD-xxxx"
export EBAY_CERT_ID="PRD-xxxx"
curl -s -X POST "https://api.ebay.com/identity/v1/oauth2/token" \
  -u "$EBAY_APP_ID:$EBAY_CERT_ID" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&scope=https://api.ebay.com/oauth/api_scope"

You should get JSON with an access_token that expires in 7,200 seconds. The default Browse API quota is 5,000 calls per day, which shapes how often the scanner can run (more on that below).

Step 2: Build the watchlist

The scanner is only as good as the list of things it watches. Good targets have three properties:

  • A long or awkward brand name that people misspell: Sennheiser, Beyerdynamic, Vitamix, Le Creuset, Fujifilm, Hasselblad, Lululemon, Swarovski, Herman Miller, Bang & Olufsen, Pokemon.
  • A used resale value of $150 to $1,000. Under that, the margin is too thin. Over that, fraud and return risk climb fast.
  • Fast sell-through: check in Seller Hub, Research (Terapeak), which is free for sellers. You want items that sell many times per week, so you can resell within days.

Avoid as a beginner: luxury watches, designer bags, sneakers and anything heavily faked (see the risk section), plus phones and laptops that can be activation-locked.

Create watch.json:

[
  {"name": "Sennheiser HD 660S2", "brand": "sennheiser", "model": "hd 660", "resale": 300, "ship_out": 14, "ship_in": 12},
  {"name": "Vitamix 5200", "brand": "vitamix", "model": "5200", "resale": 220, "ship_out": 25, "ship_in": 20},
  {"name": "Le Creuset 5.5 qt", "brand": "creuset", "model": "dutch oven", "resale": 190, "ship_out": 28, "ship_in": 25},
  {"name": "Beyerdynamic DT 1990 Pro", "brand": "beyerdynamic", "model": "dt 1990", "resale": 330, "ship_out": 14, "ship_in": 12}
]

The resale value is yours to fill in from Terapeak sold data, conservatively. Update it every couple of weeks; used prices move. The numbers above are placeholders, not current market prices.

Keep brand to a single word (for Le Creuset, misspell “creuset” and keep the rest as the model). The scanner puts the misspellings inside an eBay OR group, and single words behave predictably there.

Step 3: The scanner

Save as typo_sniper.py:

#!/usr/bin/env python3
"""Scan eBay auctions for misspelled listings and alert profitable ones to Telegram."""
import base64
import json
import os
import sqlite3
from datetime import datetime, timezone

import requests

APP_ID = os.environ["EBAY_APP_ID"]
CERT_ID = os.environ["EBAY_CERT_ID"]
TG_TOKEN = os.environ["TG_TOKEN"]
TG_CHAT = os.environ["TG_CHAT"]
MARKET = os.getenv("EBAY_MARKET", "EBAY_US")
SEARCH_URL = "https://api.ebay.com/buy/browse/v1/item_summary/search"
SEP = ","

FEE_RATE = 0.136       # most US categories at time of writing; check yours
FEE_FIXED = 0.40       # per order over $10
TAX = 0.08             # sales tax: paid on your buys, and counted in eBay's fee base
MIN_PROFIT = 40        # dollars per flip, after everything
MAX_HOURS_LEFT = 12    # only alert on auctions ending soon
BAD_WORDS = ["box only", "for parts", "not working", "case for", "cover for",
             "replacement", "empty box", "manual only", "icloud", "locked",
             "read description", "as is", "broken"]

# A few typos generic rules never produce. Add your own as you find them.
EXTRA_TYPOS = {
    "sennheiser": ["senheiser", "sennhiser", "sennheisser", "senhiser"],
    "vitamix": ["vitamax", "vitimix", "vitamex"],
    "creuset": ["cruset", "crueset", "creusset"],
    "beyerdynamic": ["beyerdinamic", "beyerdynamik", "byerdynamic", "beyerdymanic"],
}


def typos(word):
    """Dropped, doubled and swapped letters: the three most common typing errors."""
    w = word.lower()
    out = set()
    for i in range(len(w)):
        out.add(w[:i] + w[i + 1:])                         # dropped letter
        out.add(w[:i] + w[i] + w[i:])                      # doubled letter
        if i < len(w) - 1:
            out.add(w[:i] + w[i + 1] + w[i] + w[i + 2:])   # swapped letters
    out.discard(w)
    out.update(EXTRA_TYPOS.get(w, []))
    # drop variants that still contain the real spelling ("ssennheiser"): no edge there
    return sorted(t for t in out if len(t) >= 4 and " " not in t and w not in t)


def chunks(words, model, max_len=95):
    """Pack typos into OR groups like "(senheiser,sennhiser) hd 660" under 100 chars."""
    batch = []
    for w in words:
        if len(f"({SEP.join(batch + [w])}) {model}") > max_len and batch:
            yield f"({SEP.join(batch)}) {model}"
            batch = []
        batch.append(w)
    if batch:
        yield f"({SEP.join(batch)}) {model}"


def get_token():
    auth = base64.b64encode(f"{APP_ID}:{CERT_ID}".encode()).decode()
    r = requests.post(
        "https://api.ebay.com/identity/v1/oauth2/token",
        headers={"Authorization": f"Basic {auth}",
                 "Content-Type": "application/x-www-form-urlencoded"},
        data={"grant_type": "client_credentials",
              "scope": "https://api.ebay.com/oauth/api_scope"},
        timeout=20,
    )
    r.raise_for_status()
    return r.json()["access_token"]


def search_auctions(token, q):
    r = requests.get(
        SEARCH_URL,
        headers={"Authorization": f"Bearer {token}",
                 "X-EBAY-C-MARKETPLACE-ID": MARKET},
        params={"q": q, "filter": "buyingOptions:{AUCTION}",
                "sort": "endingSoonest", "limit": 200},
        timeout=30,
    )
    r.raise_for_status()
    return r.json().get("itemSummaries", [])


def money(obj):
    return float(obj["value"]) if obj else 0.0


def max_bid(item_cfg, ship_in):
    resale = item_cfg["resale"]
    net = resale - resale * (1 + TAX) * FEE_RATE - FEE_FIXED - item_cfg["ship_out"]
    return round((net - MIN_PROFIT) / (1 + TAX) - ship_in, 2)


def tg(text):
    requests.post(f"https://api.telegram.org/bot{TG_TOKEN}/sendMessage",
                  data={"chat_id": TG_CHAT, "text": text,
                        "disable_web_page_preview": "true"}, timeout=20)


def main():
    db = sqlite3.connect("seen.db")
    db.execute("CREATE TABLE IF NOT EXISTS seen (id TEXT PRIMARY KEY)")
    token = get_token()
    now = datetime.now(timezone.utc)
    calls = 0

    for cfg in json.load(open("watch.json")):
        brand = cfg["brand"].lower()
        for q in chunks(typos(brand), cfg["model"]):
            calls += 1
            for it in search_auctions(token, q):
                title = it["title"].lower()
                if brand in title.replace(" ", ""):
                    continue      # correctly spelled somewhere: no edge
                if any(b in title for b in BAD_WORDS):
                    continue
                end = datetime.fromisoformat(it["itemEndDate"].replace("Z", "+00:00"))
                hours_left = (end - now).total_seconds() / 3600
                if hours_left > MAX_HOURS_LEFT:
                    continue
                bid = money(it.get("currentBidPrice") or it.get("price"))
                ship = it.get("shippingOptions") or [{}]
                ship_in = money(ship[0].get("shippingCost")) if ship[0].get("shippingCost") else cfg["ship_in"]
                ceiling = max_bid(cfg, ship_in)
                if bid >= ceiling:
                    continue
                if db.execute("SELECT 1 FROM seen WHERE id=?", (it["itemId"],)).fetchone():
                    continue
                db.execute("INSERT INTO seen VALUES (?)", (it["itemId"],))
                db.commit()
                name, raw, url = cfg["name"], it["title"], it["itemWebUrl"]
                nbids = it.get("bidCount", 0)
                tg(f"TYPO HIT: {name}\n{raw}\n"
                   f"Bid now: ${bid:.2f} ({nbids} bids) + ship ${ship_in:.2f}\n"
                   f"MAX BID: ${ceiling:.2f}\nEnds in {hours_left:.1f}h\n{url}")
    print(f"{now:%H:%M} done, {calls} search calls")


if __name__ == "__main__":
    main()

What it does, in order:

  • Generates every dropped, doubled and swapped-letter variant of each brand (“sennheiser” gives “senheiser”, “sennheisr”, “snenheiser” and so on), plus your hand-collected extras.
  • Packs them into OR queries such as (senheiser,sennhiser,sennheisr) hd 660 so one API call covers 6 to 11 typos.
  • Skips any listing whose title also contains the correct spelling (the seller fixed it, or used both), anything that matches the junk words, and anything ending more than 12 hours from now.
  • Computes your ceiling from the formula above and alerts only when the current bid is under it.
  • Remembers alerted items in SQLite so you do not get pinged twice.

Run it once by hand. Keep typo_sniper.py and watch.json in ~/sniper (the same folder as the venv) so cron finds them later. To get TG_CHAT, send your bot any message, then open https://api.telegram.org/bot<TG_TOKEN>/getUpdates and copy the chat.id number.

python3 -m venv ~/sniper && source ~/sniper/bin/activate
pip install requests
cd ~/sniper
export EBAY_APP_ID="YourApp-xxxx-PRD-xxxx" EBAY_CERT_ID="PRD-xxxx"
export TG_TOKEN="123456:ABC..."  TG_CHAT="your_chat_id"
python typo_sniper.py

Step 4: Schedule it inside the API quota

Each brand produces roughly 20 to 40 typo variants, so about 2 to 6 calls per watchlist item (Sennheiser: 25 variants, 4 calls; Beyerdynamic: 36 variants, 6 calls). With 25 items that is around 100 calls per run. At 5,000 calls/day you can run every 30 minutes (48 runs x 100 = 4,800) and stay inside quota.

Cron does not support backslash line continuation, so put the environment in a small wrapper script, ~/sniper/run.sh:

#!/bin/sh
cd "$HOME/sniper" || exit 1
export EBAY_APP_ID="..." EBAY_CERT_ID="..." TG_TOKEN="..." TG_CHAT="..."
./bin/python typo_sniper.py >> run.log 2>&1

Then chmod 700 ~/sniper/run.sh (it holds your secrets), run crontab -e, and add one line:

*/30 * * * * /home/you/sniper/run.sh

If you need more volume, eBay lets you apply for a higher call limit through the Application Growth Check in the developer portal. Most people never need it; a tight watchlist beats a huge one.

Step 5: The 60-second check before you bid

The bot finds candidates. You still decide. For every alert:

  1. Search the correct spelling on eBay yourself. If the typo listing shows up anyway, eBay’s spelling correction already covers it and you have no edge. Expect other bidders.
  2. Check sold comps in Terapeak for the exact model and condition. Update resale in watch.json if it moved.
  3. Read the full listing and photos. Look for “untested”, missing parts, mismatched serials or stock photos.
  4. Check the seller. Feedback count, recent negatives, and location. A brand-new seller with zero feedback selling a $600 camera at a typo price is a warning, not a gift.
  5. Snipe at the max bid. Enter it in Gixen, or place it manually in the last minute. eBay uses proxy bidding, so your max is only used if someone pushes you there.

Step 6: Relist properly

This is where the profit gets made, so do it right: the full correct brand and model in the title (eBay allows 80 characters), the correct category and item specifics, 8 to 12 clean photos on a plain background, honest condition notes, and a Buy It Now price just under the lowest comparable sold price. List within 48 hours of the item arriving; capital that sits on a shelf earns nothing.

Honest cost breakdown

CostMonthlyNotes
eBay API, Telegram, Gixen free tier$0
VPS (or $0 on a home PC/Pi)$4 to $6
Packing supplies$20 to $60Scales with volume
eBay feesabout 13.6% + $0.40 per saleMost US categories, check yours
Returns and lossesbudget 5% to 10% of revenueYou will eat some returns, scratches and one bad buy
Capital$1,000 to $3,000Recycled, not spent, but tied up 1 to 3 weeks per item

New sellers: eBay typically holds payouts on your first sales (often released after delivery, usually within 30 days), and new accounts get monthly selling limits. Plan the capital for that, or build some selling history first with stuff from your own closet.

Realistic earnings

There is no public, verifiable data on what typo flippers earn, so treat these as scenarios built from the per-flip math above, not promises. The main limit is supply: many alerts will fail the 60-second check, and you will lose a lot of the auctions you do bid on.

  • Part-time (5 to 8 hours/week, $1,000 to $1,500 capital): 5 to 15 typo flips/month that survive the checks, at $30 to $80 profit each after returns. Roughly $150 to $1,000/month.
  • Serious side hustle (15 to 20 hours/week, $3,000+ capital): 20 to 40 flips/month, which in practice means mixing typo hits with other badly listed auctions (wrong category, bad photos, odd end times). Roughly $800 to $2,500/month.
  • Beyond that, it is a reselling business with a packing station and a long watchlist, not a bot that prints money. Your bottleneck becomes photos and shipping, not finding deals.

The first month is usually the worst, and may lose money: your resale estimates are off, you overpay a couple of times, and payouts are held. Judge the system by month three.

Risks: what can actually go wrong

Counterfeits (the big one). Here is what most “typo arbitrage” guides skip: counterfeiters misspell brand names on purpose so their listings slip past eBay’s brand filters and VeRO takedowns. A misspelled luxury item is more likely to be fake, not less. If you resell a fake, even unknowingly, you face account suspension, a forced refund, and in many countries selling counterfeit goods is a legal problem. Stay in categories where fakes are rare or easy to spot, check serials, and if in doubt, do not bid. This is why the watchlist above avoids watches, bags and sneakers.

Returns under the eBay Money Back Guarantee. Buyers can open an “item not as described” case, and eBay usually sides with the buyer. Describe condition precisely and photograph serial numbers and any flaws before you ship.

Seller cancels. A seller who notices the bad title can cancel your bid or end the listing early (ending early is restricted in the last 12 hours once there are bids). Annoying, not dangerous.

ToS lines you should not cross. Sniping and buying low are fine. What gets accounts banned: bidding on your own items or with friends (shill bidding, also illegal in many places), using a second account to get around selling limits or a suspension, and scraping eBay pages instead of using the API. The API itself comes with a License Agreement: use the data for your own buying decisions, do not republish it as a data product.

Taxes. Flipping for profit is business income. In the US, payment platforms send a 1099-K once you pass $20,000 and 200 transactions in a year (the threshold was restored by 2025 legislation), but the income is taxable from the first dollar either way. Keep a spreadsheet of every buy, sale and fee.

Reality check: what does not work

  • Typos alone at scale. eBay’s search matches more misspellings every year, and other flippers run typo tools too. Many alerts will be listings that eBay already shows to everyone. The typo scanner is a filter, not a guarantee. Use the step 5 check.
  • Trusting asking prices. Active Buy It Now listings are wishes. Only sold prices count. Most people who lose money here priced off active listings.
  • Ignoring shipping weight. A cast-iron dutch oven costs a lot to ship both ways. Those $25 + $28 numbers in the watchlist are there for a reason.
  • Chasing high-ticket items early. One bad $900 camera wipes out ten good $60 flips. Start in the $150 to $400 band.
  • Bidding above max “just this once”. The formula is the business. Every dollar above max_bid comes out of your margin.
  • Sitting on inventory. If something has not sold in 30 days, cut the price and move on. Capital speed matters more than squeezing the last $10.

Start today

  1. Create your eBay developer keyset and confirm the curl token test works.
  2. Open Terapeak and pick 10 items in the $150 to $400 used band that sell several times a week. Write them into watch.json with conservative resale prices.
  3. Create a Telegram bot, run typo_sniper.py by hand once, then put it on a 30-minute cron.
  4. For two weeks, only bid on alerts that pass the 60-second check, and only at max_bid.
  5. Log every flip: buy price, sale price, fees, shipping, days to sell. After 10 flips you will know your real margin, and whether to add capital, add brands, or drop the categories that do not work.