Lead classification

Sort every message by the kind of lead who wrote it

A lead type is not a harm and not a subject, so it has an axis of its own. ToxicFilter reads each message, or everything one person wrote across a thread, scores eleven types on their own, and leaves the decision to the rules you write for the types you care about.

How it works

  1. The message arrives

    A contact form, an inbox or a thread between two users. Send one message to /v1/text, or the whole thread to /v1/conversation with an author on each message.

  2. Each type is scored

    Eleven types, each made of families of phrases in eight languages. One family scores 0.45, two 0.70, three or more 0.90, and every type is scored on its own, because one person is often several at once.

  3. Your rules decide

    A type decides nothing until a rule gives it a line. Add one in the policy editor with "The lead is", or send rules.leads with the call, and choose review or block.

  4. You read the answer

    Every answer carries leads beside scores and topics, and a type that crossed a line is named in flagged as lead:type. The SDKs read it with leads and lead(type).

See it decide

  1. 01 A pitch with two families
  2. 02 Free work for equity, with pressure
  3. 03 Three missed calls
  4. 04 An idea with a budget
  5. 05 Salary plus equity is a job

A pitch with two families POST /v1/text

Hello, we offer web development and app development for growing companies.

review 7 ms

An offer and a list of services: two families, 0.70 as a sales pitch. The contact form template holds sales pitches from 0.60 and refuses them from 0.85, which takes three families or more.

The answer, abridged
{
  "decision": "review",
  "flagged": [
    "lead:sales_pitch"
  ],
  "leads": {
    "sales_pitch": 0.7
  },
  "signals": [],
  "model": {
    "read": false
  },
  "took_ms": 7
}

Free work for equity, with pressure POST /v1/conversation

founder I have an idea for an app and I need a technical cofounder.

agency What is your budget?

founder No salary for now, you get shares in the company. I have invested years in this and the previous developers were useless.

review 23 ms

Only the founder's messages are read. An idea, the equity and the missing pay are three families, 0.90, and blaming earlier developers adds pressure on top, 0.95. The template holds it and never refuses it: whether to answer is yours.

The answer, abridged
{
  "decision": "review",
  "flagged": [
    "lead:free_work_for_equity"
  ],
  "leads": {
    "free_work_for_equity": 0.95
  },
  "signals": [],
  "model": {
    "read": false
  },
  "took_ms": 23
}

Three missed calls POST /v1/conversation

client Sorry I missed the call, something went wrong with the scheduling on my side.

agency No problem, Thursday at 10?

client Apologies for missing it again, I couldn't join. Can we reschedule?

agency Sure, Monday at 4.

client Sorry, I was not able to join today. Can we find another slot next week?

allow 8 ms

Counted per message of that person, not per phrase: three apologies score 0.90 as never turns up, and it comes back in leads. The template has no line for this type, so it decides nothing until you add a rule.

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

An idea with a budget POST /v1/text

I have an idea for an online shop for my bakery. Our budget is about 15,000 EUR and we would like to launch in spring. Could you send a quote?

allow 7 ms

"I have an idea" is one family of free work for equity, and the type needs the equity or the missing pay to go further. It stays at 0.45, under every line, and the quote request goes through.

The answer, abridged
{
  "decision": "allow",
  "flagged": [],
  "leads": {
    "free_work_for_equity": 0.45
  },
  "signals": [],
  "model": {
    "read": false
  },
  "took_ms": 7
}

Salary plus equity is a job POST /v1/text

We are a fintech startup hiring a senior engineer: competitive salary plus equity, remote, starting in January.

allow 9 ms

"Salary plus equity" is wording that settles the question, so free work for equity is silenced and no type is reported at all.

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

See it decide

A pitch with two families POST /v1/text

Hello, we offer web development and app development for growing companies.

review 7 ms

An offer and a list of services: two families, 0.70 as a sales pitch. The contact form template holds sales pitches from 0.60 and refuses them from 0.85, which takes three families or more.

The answer, abridged
{
  "decision": "review",
  "flagged": [
    "lead:sales_pitch"
  ],
  "leads": {
    "sales_pitch": 0.7
  },
  "signals": [],
  "model": {
    "read": false
  },
  "took_ms": 7
}

Free work for equity, with pressure POST /v1/conversation

founder I have an idea for an app and I need a technical cofounder.

agency What is your budget?

founder No salary for now, you get shares in the company. I have invested years in this and the previous developers were useless.

review 23 ms

Only the founder's messages are read. An idea, the equity and the missing pay are three families, 0.90, and blaming earlier developers adds pressure on top, 0.95. The template holds it and never refuses it: whether to answer is yours.

The answer, abridged
{
  "decision": "review",
  "flagged": [
    "lead:free_work_for_equity"
  ],
  "leads": {
    "free_work_for_equity": 0.95
  },
  "signals": [],
  "model": {
    "read": false
  },
  "took_ms": 23
}

Three missed calls POST /v1/conversation

client Sorry I missed the call, something went wrong with the scheduling on my side.

agency No problem, Thursday at 10?

client Apologies for missing it again, I couldn't join. Can we reschedule?

agency Sure, Monday at 4.

client Sorry, I was not able to join today. Can we find another slot next week?

allow 8 ms

Counted per message of that person, not per phrase: three apologies score 0.90 as never turns up, and it comes back in leads. The template has no line for this type, so it decides nothing until you add a rule.

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

An idea with a budget POST /v1/text

I have an idea for an online shop for my bakery. Our budget is about 15,000 EUR and we would like to launch in spring. Could you send a quote?

allow 7 ms

"I have an idea" is one family of free work for equity, and the type needs the equity or the missing pay to go further. It stays at 0.45, under every line, and the quote request goes through.

The answer, abridged
{
  "decision": "allow",
  "flagged": [],
  "leads": {
    "free_work_for_equity": 0.45
  },
  "signals": [],
  "model": {
    "read": false
  },
  "took_ms": 7
}

Salary plus equity is a job POST /v1/text

We are a fintech startup hiring a senior engineer: competitive salary plus equity, remote, starting in January.

allow 9 ms

"Salary plus equity" is wording that settles the question, so free work for equity is silenced and no type is reported at all.

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

How lead classification works

The types, how each one is scored, what silences it and how to turn a score into a rule.

What a lead type is

Every answer has three axes. scores holds the harms, topics holds the subjects, and leads holds who is writing and what they want. A sales pitch is not abuse and an offer of equity instead of pay is not a subject, so neither fits the other two lists. Each of the eleven types is scored from 0 to 1 on its own, because the same person is often free work for equity, no budget and scope creep in one thread.

How each type is scored

A type is a few families of phrases. One family is how honest people write ("I have an idea" is how good projects start), so it scores 0.45. Two families score 0.70 and three or more 0.90, which is why a line around 0.60 acts on the shape and not on a word. Repeating the same point three different ways counts as one more family. Free work for equity needs its defining family, the equity or the missing pay, and free consulting needs the missing budget: without them, they stay at 0.45.

What silences a type, and what raises it

Wording that settles the question silences a type outright: "salary plus equity" is a job, a buyer asking what something would cost is not a pitch. Those phrases match exactly. Pressure works the other way: years without pay, "you only care about money" or blaming earlier providers add 0.10 to free work for equity and no budget when they are already there. Never turns up is counted per message of that person, so one apology means nothing, two score 0.70 and three 0.90.

Phrases that match loosely

Nobody writes the way a list does. A phrase of three words or more also counts when its meaningful words appear close together, in any order and with any ending. Negations and the people involved are kept, so "no budget" is never found in "we have a budget". Shorter phrases match exactly, or a single word anywhere would count.

Reading a whole conversation, and the model

Send the thread to /v1/conversation and the author of the last message is read across everything they wrote. When the call uses the model, it reads the earlier messages too, with an author on each line, and scores every type in the same call; the higher score wins. In a conversation the model reads when it adds something: when the free checks found something, when a type is half there, or every fifth message, rather than on every reply.

Turning a score into a rule

Measure first: leads is in every answer, so a month of your own traffic shows which types arrive and at what scores. Then add a rule in the policy editor ("The lead is", the type, review or block), or send one with the call: "rules": {"leads": {"sales_pitch": {"review": 0.6}}}. The contact form template already holds sales pitches from 0.60 and refuses them from 0.85, and holds job seekers, free work for equity and no budget from 0.60 without ever refusing them.

How the work is split

The instant checks settle the clear cases in about a millisecond, the model reads what depends on context, and your rules and your people have the last word.

  • Context is the model's job

    The phrase families settle the clear shapes in about a millisecond, in eight languages. When the call uses the model, it reads the whole thread and scores every type against what your business does, and the higher score wins.

  • The messages, not the person

    A lead type describes what the messages ask for, never who the person is. Every answer measures all eleven types, and your rules decide which of them acts.

  • Nothing is stored

    Nothing is remembered between calls. To read a whole thread, send the thread; what is kept is the verdict on its last message, never the conversation.

Frequently asked questions

Which lead types are there?

Eleven: free work for equity, no budget, unrealistic expectations, free consulting, scope creep, never turns up, sales pitch, partnership offer, job seeker, student or survey, and support request. Every answer measures all of them and reports the ones that scored.

How is a lead type scored?

Each type is a few families of phrases. One family scores 0.45, two 0.70, three or more 0.90, and three different phrases of the same family count as one more. Free work for equity needs the equity or the missing pay to pass 0.45, and free consulting needs the missing budget.

Can a lead type block a message?

Only if you say so. A type has no line until a rule gives it one, in the policy editor with "The lead is" or with rules.leads in the call. Rules sent alone in a call act only on what they name; sent with a policy, they are laid over it.

Does it read the whole conversation?

Through /v1/conversation, yes: everything the author of the last message wrote counts, and the other side's messages are left out, so your replies never count against them.

What does the model add?

When the call uses the model, it reads the earlier messages with who said what and scores every type for the author, judged against what your policy says your business does. The higher of its score and the phrases' wins per type, in the same call. In a conversation it reads when the free checks found something, when a type is between 0.45 and 0.70, or every fifth message.

How do I read the lead types from the SDKs?

The PHP, Python and JavaScript clients expose leads, with every type that scored, and lead(type), which returns 0 when nothing suggested that type.

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.