Moderating Product Reviews With Your Own Rules
Moderate marketplace reviews against rules you write: block spam and off-platform contact details, keep honest criticism, and hold the gray areas for review. Code in curl, TypeScript and Python.
A marketplace's reviews section faces pressure from two directions. One is spam: "Great blender, but you can get it 40% cheaper at bestdeals-example.shop, message me on WhatsApp." That review is an ad for a scam, and every hour it stays up costs trust. The other is over-moderation: a one-star review that says the lid feels flimsy is exactly what buyers need to see. A filter that removes it makes every other review look paid for.
Generic moderation tools don't know where your line is. This guide builds a moderation step around rules you write. Spam and off-platform contact are blocked, honest criticism is published, and the gray areas are held for a person. The evaluation step uses the Kovca Content Intelligence API.
Write the policy before the code
Moderation quality depends far more on the policy than on the classifier. Kovca takes three lists:
policy.disallowed: concrete things that aren't allowed, up to 20, in plain language. "Links or contact details that take buyers off the platform" is a good rule. "Inappropriate content" isn't, because nobody can check it consistently.policy.allowed: things that are explicitly fine, up to 20, and considered for every check. "Negative reviews and criticism of products or service" protects your one-star reviews.policy.block_categories: which of the default categories (spam,harassment,threats,sexual_content,violent_content,prohibited_promotion) should block outright when likely. Anything you don't list is held for review instead.
The pipeline
- 1Review submittedtext, product, reviewer account
- 2Your checksrate limits, verified purchase, duplicate text
- 3Kovca Content Intelligencedecision, categories, policy_matchesModeration queuedecision: review
- 4Publish or blockallow → live, block → tell the author why
- 5Audit logrequest_id, matched rule, moderator outcome
1Handle the cheap cases first
Some checks don't need a model. Rate-limit new accounts, reject exact-duplicate text, and require a verified purchase if your marketplace does. These are your rules, and they're cheaper and easier to explain than any classifier.
2Evaluate the review
Send the text as content (up to 5,000 characters), with content_type and context so the evaluation knows it's a product review and not a chat message. The content is treated purely as data, so instructions written inside a review are ignored.
curl
curl https://kovca.io/api/v1/content/moderate \
-H "Authorization: Bearer $KOVCA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Great blender but you can get it 40% cheaper at bestdeals-example.shop, just message me on WhatsApp +1 555 0100.",
"content_type": "product_review",
"context": "Marketplace product reviews",
"policy": {
"disallowed": ["Links or contact details that take buyers off the platform", "Reviews of a different product than the one listed"],
"allowed": ["Negative reviews and criticism of products or service"],
"block_categories": ["spam", "threats", "harassment"]
}
}'{
"request_id": "req_Lt6qN3wRv8KzM1pY",
"decision": "block",
"recommended_action": "block_submission",
"categories": [
{ "category": "spam", "assessment": "likely" },
{ "category": "policy_violation", "assessment": "likely" }
],
"policy_matches": [
{ "rule": "Links or contact details that take buyers off the platform", "source": "policy.disallowed[0]", "assessment": "likely" }
],
"signals": ["custom_rule_matched"]
}policy_matches quotes your rule exactly. That's the sentence to show the author ("Your review was not published because it includes contact details that take buyers off the platform") and the sentence a moderator reviews on appeal.
TypeScript (Node 18+)
type Assessment = "likely" | "possible";
type Moderation = {
request_id: string;
decision: "allow" | "review" | "block" | "insufficient_information";
recommended_action: "publish" | "hold_for_review" | "block_submission" | "request_information";
categories: { category: string; assessment: Assessment }[];
policy_matches: { rule: string; source: string; assessment: Assessment }[];
signals: ("default_criteria_only" | "custom_rule_matched" | "ambiguous" | "too_short_to_evaluate")[];
};
const policy = {
disallowed: ["Links or contact details that take buyers off the platform", "Reviews of a different product than the one listed"],
allowed: ["Negative reviews and criticism of products or service"],
block_categories: ["spam", "threats", "harassment"],
};
export async function moderateReview(text: string): Promise<Moderation | null> {
const res = await fetch("https://kovca.io/api/v1/content/moderate", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.KOVCA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ content: text.slice(0, 5000), content_type: "product_review", context: "Marketplace product reviews", policy }),
});
return res.ok ? res.json() : null; // null → hold for review, never auto-publish
}Python
import os, requests
POLICY = {
"disallowed": ["Links or contact details that take buyers off the platform",
"Reviews of a different product than the one listed"],
"allowed": ["Negative reviews and criticism of products or service"],
"block_categories": ["spam", "threats", "harassment"],
}
def moderate_review(text):
r = requests.post(
"https://kovca.io/api/v1/content/moderate",
headers={"Authorization": f"Bearer {os.environ['KOVCA_API_KEY']}"},
json={"content": text[:5000], "content_type": "product_review",
"context": "Marketplace product reviews", "policy": POLICY},
timeout=30,
)
return r.json() if r.ok else NoneNote the failure mode. Triage fails open (an unclassified ticket still reaches a queue), but moderation should fail closed into review. If the call fails, hold the review instead of publishing it unchecked.
3Act on the decision
The decision is the first rule that matches, in this order:
| decision | recommended_action | When | What you do |
|---|---|---|---|
insufficient_information | request_information | Too short to evaluate, like "ok" or "👍" | Publish the star rating only, or ask for more detail. |
block | block_submission | A likely match on a concrete disallowed rule, or a likely category in block_categories | Reject and show the author the matched rule. |
review | hold_for_review | Any other match, including possible ones | Queue for a moderator. Don't publish yet. |
allow | publish | Nothing matched | Publish. |
With the policy above, "Arrived two days late and the lid is a bit flimsy, but it blends smoothies well. Three stars." matches nothing disallowed and is covered by the allowed rule, so it's published. A personal attack on a named seller is blocked when harassment is assessed as likely, because harassment is in block_categories. If it's only possible, the review is held for a moderator.
4Close the loop with moderators
Log the request_id, the decision, the matched rules, and what the moderator finally did. Two patterns are worth watching for:
- A rule that's almost always overturned. It's probably worded too broadly. Narrow it, or move it from
disallowedto a note for moderators. - A category that's always upheld when held for review. That's a candidate for
block_categories.
Kovca doesn't store content, so this log is your only history. Keep it.
Rolling it out
- Shadow mode. Evaluate every new review but publish as you do today. Have moderators spot-check
blockdecisions and a sample ofallowdecisions. - Block the obvious. Turn on automatic blocking for your concrete
disallowedrules andspam. Put everything markedreviewin the queue. - Expand carefully. Add categories to
block_categoriesone at a time, based on the overturn data from step 4.
Volume and cost
One request per submitted review, or per edit. The Free plan covers 1,000 successful requests a month at 10 per minute. Pay As You Go is $0.005 per successful request at 60 per minute, with a spending cap (pricing). Content is never stored by Kovca, and the API never publishes, deletes, or suspends anything itself.
Try it with your hardest reviews
Collect a dozen reviews your moderators disagreed about, write your policy as three or four concrete rules, and run them through. Sign up free, create a key, and test in the playground. Category definitions and decision rules are in the Content Intelligence docs.