E-commerce

Let customers talk about your products, and keep the page yours

Product reviews, questions under a product and the photos customers upload, checked before they are published. ToxicFilter catches the hidden link to a cheaper copy, the review written for a free product and the card number pasted into a public question, and leaves the honest two-star review exactly where it is.

What goes wrong on platforms like yours

Your product page, someone else's advert

"Same thing for half the price, link below." A question or a review is the cheapest advertising space on the internet, and the one under your best-selling product is the most valuable. The link usually hides where it goes.

Reviews written for a free product

"Received this product for free in exchange for a review." Sometimes the reviewer says so in the review itself, and a rating built on those stops meaning anything to the next customer.

Card numbers and phones in a public question

A customer charged twice pastes the card to prove it; another leaves a phone number so somebody calls. Both meant well, and both put private data on a page anyone can read.

Photos you did not take

Customer photos are some of the most convincing content a shop has, and the one place where anything at all can be uploaded under your product's name.

What it decides, on content like yours

Judged right now by the engine the API uses, under the template for this kind of platform, with no model involved.

  1. 01 A cheaper copy in the questions
  2. 02 A review for a free product
  3. 03 An order number and a phone
  4. 04 A card number in a public question
  5. 05 A harsh but honest two-star

A cheaper copy in the questions POST /v1/text with surface: review

Does it fit a 10 cm jar? Same lantern for half the price here: https://bit.ly/lamp50

block 12 ms
  • Uses a link shortener, which hides where the link goes.

The link is a shortener, which hides where it goes, and the reviews template refuses it. A plain link to another shop is not refused for being a link: one link is how people answer questions.

The answer, abridged
{
  "decision": "block",
  "flagged": [
    "spam"
  ],
  "scores": {
    "spam": 0.7
  },
  "signals": [
    {
      "category": "spam",
      "score": 0.7,
      "reason": "Uses a link shortener, which hides where the link goes.",
      "evidence": [
        "https://bit.ly/lamp50"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 12
}

A review for a free product POST /v1/text with surface: review

I received this product for free in exchange for a review. Bright, light and the battery lasts all night.

block 8 ms
  • Says the review was written in exchange for something.

The review says itself that the product was given in exchange for it, so the rating is not worth counting. The praise alone would pass.

The answer, abridged
{
  "decision": "block",
  "flagged": [
    "spam"
  ],
  "scores": {
    "spam": 0.7
  },
  "signals": [
    {
      "category": "spam",
      "score": 0.7,
      "reason": "Says the review was written in exchange for something.",
      "evidence": [
        "in exchange for a review",
        "received this product for free"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 8
}

An order number and a phone POST /v1/text with redact: true

Order 4532015112830367 arrived with a cracked glass. Can someone call me on +44 7700 900123?

review 8 ms
  • Contains what looks like a phone number.

The sixteen-digit order number fails the card checksum, so it is not reported. The phone is, and the question is held with the number masked rather than refused.

The answer, abridged
{
  "decision": "review",
  "flagged": [
    "personal_data"
  ],
  "scores": {
    "personal_data": 0.5
  },
  "signals": [
    {
      "category": "personal_data",
      "score": 0.5,
      "reason": "Contains what looks like a phone number.",
      "evidence": [
        "447•••••••23"
      ]
    }
  ],
  "redacted": "Order 4532015112830367 arrived with a cracked glass. Can someone call me on [redacted]?",
  "model": {
    "read": false
  },
  "took_ms": 8
}

A card number in a public question POST /v1/text with surface: review

I was charged twice on card 4111 1111 1111 1111 for order 88213. Can you refund one?

block 8 ms
  • Contains 1 payment card number(s). This one is not a guess: the digits check out.

These digits pass the card checksum, so this is a card and not a guess, and it is kept off the public page. The order number beside it is not reported.

The answer, abridged
{
  "decision": "block",
  "flagged": [
    "personal_data"
  ],
  "scores": {
    "personal_data": 0.95
  },
  "leads": {
    "support_request": 0.45
  },
  "signals": [
    {
      "category": "personal_data",
      "score": 0.95,
      "reason": "Contains 1 payment card number(s). This one is not a guess: the digits check out.",
      "evidence": [
        "••••••••••••1111"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 8
}

A harsh but honest two-star POST /v1/text with surface: review

Brighter than I expected but the handle snapped after a week and the battery cover is flimsy. Two stars, would not buy again.

allow 8 ms

Negative, specific and useful to the next buyer. Nothing in it is paid for, hidden behind a link or private, so it is published as it is.

The answer, abridged
{
  "decision": "allow",
  "flagged": [],
  "signals": [],
  "model": {
    "read": false
  },
  "took_ms": 8
}

What it decides, on content like yours

Judged right now by the engine the API uses, under the template for this kind of platform, with no model involved.

A cheaper copy in the questions POST /v1/text with surface: review

Does it fit a 10 cm jar? Same lantern for half the price here: https://bit.ly/lamp50

block 12 ms
  • Uses a link shortener, which hides where the link goes.

The link is a shortener, which hides where it goes, and the reviews template refuses it. A plain link to another shop is not refused for being a link: one link is how people answer questions.

The answer, abridged
{
  "decision": "block",
  "flagged": [
    "spam"
  ],
  "scores": {
    "spam": 0.7
  },
  "signals": [
    {
      "category": "spam",
      "score": 0.7,
      "reason": "Uses a link shortener, which hides where the link goes.",
      "evidence": [
        "https://bit.ly/lamp50"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 12
}

A review for a free product POST /v1/text with surface: review

I received this product for free in exchange for a review. Bright, light and the battery lasts all night.

block 8 ms
  • Says the review was written in exchange for something.

The review says itself that the product was given in exchange for it, so the rating is not worth counting. The praise alone would pass.

The answer, abridged
{
  "decision": "block",
  "flagged": [
    "spam"
  ],
  "scores": {
    "spam": 0.7
  },
  "signals": [
    {
      "category": "spam",
      "score": 0.7,
      "reason": "Says the review was written in exchange for something.",
      "evidence": [
        "in exchange for a review",
        "received this product for free"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 8
}

An order number and a phone POST /v1/text with redact: true

Order 4532015112830367 arrived with a cracked glass. Can someone call me on +44 7700 900123?

review 8 ms
  • Contains what looks like a phone number.

The sixteen-digit order number fails the card checksum, so it is not reported. The phone is, and the question is held with the number masked rather than refused.

The answer, abridged
{
  "decision": "review",
  "flagged": [
    "personal_data"
  ],
  "scores": {
    "personal_data": 0.5
  },
  "signals": [
    {
      "category": "personal_data",
      "score": 0.5,
      "reason": "Contains what looks like a phone number.",
      "evidence": [
        "447•••••••23"
      ]
    }
  ],
  "redacted": "Order 4532015112830367 arrived with a cracked glass. Can someone call me on [redacted]?",
  "model": {
    "read": false
  },
  "took_ms": 8
}

A card number in a public question POST /v1/text with surface: review

I was charged twice on card 4111 1111 1111 1111 for order 88213. Can you refund one?

block 8 ms
  • Contains 1 payment card number(s). This one is not a guess: the digits check out.

These digits pass the card checksum, so this is a card and not a guess, and it is kept off the public page. The order number beside it is not reported.

The answer, abridged
{
  "decision": "block",
  "flagged": [
    "personal_data"
  ],
  "scores": {
    "personal_data": 0.95
  },
  "leads": {
    "support_request": 0.45
  },
  "signals": [
    {
      "category": "personal_data",
      "score": 0.95,
      "reason": "Contains 1 payment card number(s). This one is not a guess: the digits check out.",
      "evidence": [
        "••••••••••••1111"
      ]
    }
  ],
  "model": {
    "read": false
  },
  "took_ms": 8
}

A harsh but honest two-star POST /v1/text with surface: review

Brighter than I expected but the handle snapped after a week and the battery cover is flimsy. Two stars, would not buy again.

allow 8 ms

Negative, specific and useful to the next buyer. Nothing in it is paid for, hidden behind a link or private, so it is published as it is.

The answer, abridged
{
  "decision": "allow",
  "flagged": [],
  "signals": [],
  "model": {
    "read": false
  },
  "took_ms": 8
}

What it looks for

Each a score of its own, with its own lines, and the reason in a sentence whenever one acts.

The template you start from

Pick it when you create a policy and these rules are written for you, ready to edit. Everything it does not mention keeps following our defaults.

  • Spam holds at 0.35 · refuses at 0.65
  • Filter evasion holds at 0.35 · refuses at 0.70
  • Gibberish holds at 0.45 · never refuses
  • Personal data holds at 0.30 · refuses at 0.80
  • Sexual content holds at 0.45 · refuses at 0.75
  • Violence holds at 0.40 · refuses at 0.75

Set by the template

One call before you publish

Send the text with where it will appear, and act on the decision.

The request

curl https://toxicfilter.com/api/v1/text \
  -H "Authorization: Bearer $TOXICFILTER_KEY" \
  -d content="Does it fit a 10 cm jar? Same lantern for half the price here: https://bit.ly/lamp50" \
  -d surface=review
The full reference →

A month, in numbers

Items checked
10,000
Read by the model
800
Images
600
Credits, about
21,600

Fits in the Plus plan. See the plans →

A guide to moderating an online shop

What to check on a product page, when to check it and how to set the rules, for reviews, questions and customer photos.

How to moderate product reviews and questions

Call the API when a review or a question is submitted, with surface: review, and act on one of three answers: publish it, hold it for a person, or refuse it. Nearly everything under a product is somebody asking about the size or saying it arrived on time, and the free checks settle that in about a millisecond, so the customer waits for nothing. The model only reads what those checks leave open. What lands in review waits in a queue, in your dashboard or through the API, and a signed webhook tells your shop when somebody approves it.

The questions under a product are read by people about to buy, which is exactly why they attract links to a cheaper copy. ToxicFilter does not refuse a link for being a link, because one link in a sentence is how customers answer each other. What it reports as spam is the shape of promotion: a link through a shortener that hides where it goes, a message that opens with a link, several links carried by a few words. The reviews template refuses those. If you want no outside links at all, put the domains in your own word list.

Reviews written for a free product

On the review surface, a review that says it was written in exchange for something ("received this product for free", "in exchange for a review") scores 0.70 on spam, and the reviews template refuses it. A review that reads like the shop writing about itself ("our shop", "use code") needs two of those tells and is held for a person. Copies of the same review posted again and again, even reworded, are counted between calls, per account and per project.

Order numbers, card numbers and phones

Customers paste what they think will help: the order number, the phone to call them on, sometimes the card that was charged twice. The reviews template holds personal data for a person and refuses a card. Cards are validated by their checksum and a real issuer prefix, so a sixteen-digit order reference is not mistaken for one. With redact, the answer carries the same text with the phone, the email, the IBAN or the card masked, so a useful question can still be published.

Checking the photos customers upload

Send each picture with POST /v1/image, by URL or as the file itself, before it reaches your storage. The model reads every picture, which is why a photo costs more than a text check. The words visible in the photo are run through the same free text checks as a review, so a phone number or a link written on a piece of card is found too. A picture already refused here is recognised again by its fingerprint without paying the model a second time.

Choosing the thresholds for your shop

Start from the reviews template, which holds and refuses spam, disguised text and personal data sooner than the shipped lines, and holds gibberish for a person without ever refusing it. Then try any change as a second policy running beside the one in force: both verdicts are computed on your own reviews and questions, only one acts, and the dashboard shows where they disagree before you switch.

Frequently asked questions

How do I stop competitors posting links in my product questions?

Send each question and review before publishing it. A link through a shortener, a message that opens with a link and several links in a few words are reported as spam, and the reviews template refuses them. One ordinary link in a sentence is how people answer each other, so it is not refused for being a link; if you do not want any, add the domains to your own word list.

Will it remove negative reviews of my products?

No. Nothing here scores how unhappy a customer is. A two-star review that says the handle broke is published; what is acted on is a review written for something in return, a hidden link, private data or an insult aimed at a person.

Will an order number be mistaken for a card?

No. Card numbers are validated by their checksum and a real issuer prefix, and IBANs by their own check digits, so a sixteen-digit order reference is not reported as a card. A real card number is, and with redact the answer carries the text with it masked.

Can it check the photos customers upload?

Yes, with POST /v1/image, by URL or with the file itself. The model reads every picture, so each one costs more than a text check, and the words visible in it go through the same text checks as a review. A picture you already refused is recognised again without paying the model to look.

How much does it cost for a shop?

A check costs one credit, and when the model reads it the tokens it used are added, about eight in all for a review and ten for a picture. Most reviews and questions are settled by the free checks, so the bill follows how many photos you get and how much of the text is ambiguous.

Do I have to tell a customer why their review was removed?

In the EU, yes: article 17 of the Digital Services Act requires a statement of reasons for every removal or restriction, whatever the size of the platform. ToxicFilter can write it with every blocked review or question.

Try it on your own traffic

2,000 credits a month on the free plan, no card. Enough to send a week of your own content and see what it says about it.