On a marketplace, your catalog is built from images other people upload, whether from third-party sellers on a structured e-commerce site or from individuals posting ads on a classifieds platform. At any real volume you cannot review them by hand, and you cannot afford not to. Prohibited or offensive images do show up: weapons, drugs, explicit material, a hate symbol printed on a t-shirt. One of them reaching the storefront is a brand problem, a payment-processor problem, and sometimes a legal one. The risk runs higher on classifieds, where uploads come from individuals rather than vetted sellers.
A content moderation API screens every upload automatically and sends only the ambiguous ones to a human. This guide builds that for an e-commerce catalog in Python: scoring a listing image, a policy that passes legitimate product categories while blocking prohibited ones, a gate at listing creation, and a bulk backfill. All code runs against a live API.
Want to try it first? Screen an image with the content moderation API on your own listings.
Why a Generic Nudity Filter Hurts a Marketplace
The instinct is to bolt on a nudity detector and block anything it flags. On a marketplace that backfires. An apparel seller listing a bikini, a lingerie brand, a swimwear store: a blanket nudity filter rejects all of them and you lose real sellers and real sales. The same photo that is a violation on a social feed is a legitimate product on a fashion marketplace.
So marketplace moderation is not "block flagged images." It is "apply a policy that knows your catalog": reject genuinely prohibited content, let category-relevant product imagery through, and send the in-between to a fast human review. That needs a moderation API that returns which category fired and how confidently, not a single score.
Step 1: Score a Listing Image
Send the seller's image and get back the labels the API is confident about. A clean product photo returns an empty list:
import requests
HEADERS = {
"x-rapidapi-key": "YOUR_API_KEY",
"x-rapidapi-host": "nsfw-detect3.p.rapidapi.com",
}
def moderate(image_path):
"""Return the moderation labels for an image (empty list means clean)."""
with open(image_path, "rb") as f:
r = requests.post(
"https://nsfw-detect3.p.rapidapi.com/nsfw-detect",
headers=HEADERS,
files={"image": f},
)
return r.json()["body"]["ModerationLabels"]
Each label has a Name, a Confidence (0 to 100), and a ParentName. Labels are hierarchical, so the top-level categories are the ones with an empty ParentName. Print them on a sample of your real listings to see the exact names you need:
labels = moderate("seller_photo.jpg")
categories = [l["Name"] for l in labels if l["ParentName"] == ""]
print(categories)
# ['Swimwear or Underwear'] on a bikini listing (legit for apparel)
# ['Drugs & Tobacco'] on a prohibited listing
# [] on a clean product photo
Step 2: A Policy That Knows Your Catalog
Map each category to a listing action, and make the category-relevant ones review or approve instead of a hard reject. An apparel marketplace reviews swimwear rather than blocking it; a wine marketplace allows alcohol:
# Tune per marketplace. A category absent from the table is approved.
LISTING_POLICY = {
"Explicit": {"action": "reject", "threshold": 50},
"Non-Explicit Nudity of Intimate parts and Kissing": {"action": "reject", "threshold": 60},
"Violence": {"action": "reject", "threshold": 60},
"Hate Symbols": {"action": "reject", "threshold": 40},
"Drugs & Tobacco": {"action": "reject", "threshold": 60},
# Category-relevant for apparel -> review, do not auto-reject
"Swimwear or Underwear": {"action": "review", "threshold": 60},
"Gambling": {"action": "review", "threshold": 70},
# Alcohol absent -> approved (wine and spirits sellers)
}
def screen_listing(labels):
"""Map moderation labels to a verdict: approve, review, or reject."""
verdict, reasons = "approve", []
for label in labels:
if label["ParentName"]: # top-level categories only
continue
rule = LISTING_POLICY.get(label["Name"])
if not rule or label["Confidence"] < rule["threshold"]:
continue
reasons.append(f"{label['Name']} ({label['Confidence']:.0f}%)")
if rule["action"] == "reject":
verdict = "reject"
elif verdict != "reject":
verdict = "review"
return verdict, reasons
print(screen_listing(moderate("leather_shoe.jpg"))) # ('approve', [])
print(screen_listing(moderate("bikini.jpg"))) # ('review', ['Swimwear or Underwear (97%)'])
print(screen_listing(moderate("prohibited.jpg"))) # ('reject', ['Drugs & Tobacco (100%)'])
The bikini listing lands in review, not the bin, so a human confirms a legitimate apparel product in seconds instead of the system silently killing the sale. One table, edited per marketplace, serves a fashion store, a wine merchant, or a general bazaar.
Building a trust-and-safety layer? The content moderation API has a free tier to test on your catalog.
Step 3: Gate at Listing Creation
A listing usually has several images, and it should be gated by its worst one. Screen them all on submit, strictest verdict wins:
RANK = {"approve": 0, "review": 1, "reject": 2}
def on_new_listing(listing_id, image_paths):
"""Screen every image on a new listing; the strictest verdict wins."""
verdict, reasons = "approve", []
for path in image_paths:
v, r = screen_listing(moderate(path))
reasons += r
if RANK[v] > RANK[verdict]:
verdict = v
if verdict == "approve":
publish_listing(listing_id) # goes live
elif verdict == "review":
queue_for_review(listing_id, reasons) # human checks
else:
reject_listing(listing_id, reasons) # seller is notified why
return verdict
Returning a clear reason on a reject matters: the seller sees why and can fix the photo instead of opening a support ticket.
Step 4: Backfill the Existing Catalog
Listings created before you added the check still need screening. Run a one-time batch concurrently, and act on each result as it lands:
from concurrent.futures import ThreadPoolExecutor, as_completed
def screen_one(listing):
try:
verdict, reasons = screen_listing(moderate(listing["image"]))
except Exception as e:
verdict, reasons = "review", [f"moderation error: {e}"]
return {"id": listing["id"], "verdict": verdict, "reasons": reasons}
def backfill(catalog, max_workers=10):
flagged = []
with ThreadPoolExecutor(max_workers=max_workers) as pool:
futures = [pool.submit(screen_one, l) for l in catalog]
for fut in as_completed(futures):
res = fut.result()
if res["verdict"] != "approve":
flagged.append(res) # pull or review these
return flagged
flagged = backfill(all_listings)
print(f"{len(flagged)} listings need attention")
Failing safe to review on error keeps a failed call from quietly approving a listing.
Honest Limits
- It does not catch counterfeits or IP misuse. A fake handbag looks like a real one. Brand rights and authenticity need seller verification and brand registries, not a pixel score.
- Restricted does not equal unsafe. Whether a legal product can be sold in a given country is a category-and-jurisdiction rule, separate from whether the image is offensive.
- Thresholds are yours to tune. Too strict and you bury reviewers in legitimate apparel; too loose and prohibited items slip through. Tune against your own catalog.
For moderating non-commerce user uploads (profile photos, chat images, review photos), see the content moderation guide for user uploads, and for a provider comparison, the best content moderation APIs comparison.
Getting Started
Grab a key from the content moderation API page, run moderate over a sample of your real listings to see which categories fire, and set LISTING_POLICY so legitimate product categories review or pass while prohibited ones reject. Wire it into listing creation, backfill once, and prohibited products stop reaching your storefront. A free tier is available to test first.
Read the full tutorial on ai-engine.net.
Top comments (0)