Qualifying and Routing Inbound Leads in Minutes
Qualify inbound demo requests against your own criteria, route them to the right territory, and keep research and support requests out of sales queues. Code in curl, TypeScript and Python.
On Tuesday at 4:52 p.m., a VP at a distribution company fills in your contact form. They're replacing their tool for 120 reps, need the Salesforce integration, and want to sign before the end of next month. The form goes into a shared inbox along with a student's research question, a password reset, and a two-person bakery asking for a quote. Someone gets to it Thursday. By then, the VP has had two demos with your competitors.
Speed-to-lead is mostly a routing problem. This guide builds a qualification step that runs as soon as a form is submitted. It applies your criteria, picks the territory, and gets the purchase-ready leads to a rep while they're still at their desk. The qualification step uses the Kovca Lead Intelligence API.
Qualify on what they said, against what you sell
Lead scoring often goes wrong in one of two ways. It scores the person, using signals you shouldn't use, or it guesses at things nobody stated. This design avoids both:
- Intent comes from the message. Is this a purchase, an evaluation, research, or not a sales inquiry at all?
- Fit comes from your criteria. Minimum budget and served countries are checked exactly, in code. Plain-language criteria such as "needs at least 50 seats" are checked one at a time, each with its own result.
- Nothing is inferred. Budget, authority, and company size that weren't stated aren't guessed. The API has no fields for personal or demographic traits and doesn't use them.
The pipeline
- 1Form or chatmessage, company, budget, timeline
- 2Your enrichmentoptional: CRM match, existing-customer check
- 3Kovca Lead Intelligencequalification, intent, fit, territorySupport / successexisting customer or not a sales inquiry
- 4CRMowner by territory, priority by qualification
- 5Rep alertSlack or email for hot leads only
1Capture the facts you'll judge on
Send what the form collected, without filling in gaps. message is required (up to 4,000 characters). company.country must be a two-letter ISO code. budget is an amount and a currency. If your form doesn't ask for a budget, leave it out, and missing_information will tell you it would help.
Put your rules in qualification_criteria and routing_rules. They travel with every request, so changing a criterion is a config change, not a model retrain.
2Qualify
curl
curl https://kovca.io/api/v1/leads/qualify \
-H "Authorization: Bearer $KOVCA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "We'\''re replacing our current tool for 120 sales reps and need the Salesforce integration. Can you send pricing for an annual plan? We want to sign before the end of next month.",
"company": { "name": "Northwind Traders", "industry": "Wholesale distribution", "size": "500-1000", "country": "US" },
"budget": { "amount": 60000, "currency": "USD" },
"product_interest": "Sales analytics",
"qualification_criteria": {
"min_budget": { "amount": 20000, "currency": "USD" },
"countries": ["US", "CA", "GB", "DE"],
"criteria": ["Needs at least 50 user seats", "Uses Salesforce or plans to integrate with it"]
},
"routing_rules": [
{ "territory": "North America", "countries": ["US", "CA"] },
{ "territory": "EMEA", "countries": ["GB", "DE", "FR"] }
]
}'{
"request_id": "req_Fy4pV8nKs2QwL6tM",
"qualification": "hot",
"intent": "purchase_ready",
"fit": "meets_criteria",
"recommended_action": "prioritize_followup",
"territory": "North America",
"signals": ["explicit_buying_intent", "near_term_timeline", "budget_stated", "requirements_stated"],
"criteria_results": [
{ "criterion": "min_budget", "result": "met", "evidence": "qualification_criteria.min_budget" },
{ "criterion": "countries", "result": "met", "evidence": "qualification_criteria.countries" },
{ "criterion": "Needs at least 50 user seats", "result": "met", "evidence": "qualification_criteria.criteria[0]" },
{ "criterion": "Uses Salesforce or plans to integrate with it", "result": "met", "evidence": "qualification_criteria.criteria[1]" }
],
"missing_information": []
}Every criterion is reported separately. When a rep asks why a lead was marked hot, the answer is in criteria_results.
TypeScript (Node 18+)
type LeadResult = {
request_id: string;
qualification: "hot" | "warm" | "cold" | "unqualified" | "insufficient_information";
intent: "purchase_ready" | "evaluating" | "information_request" | "existing_customer_or_support" | "not_a_sales_inquiry" | "unclear";
fit: "meets_criteria" | "does_not_meet" | "unknown" | "not_evaluated";
recommended_action: "prioritize_followup" | "standard_followup" | "nurture" | "request_information" | "manual_review";
territory: string | null;
signals: string[];
criteria_results: { criterion: string; result: "met" | "not_met" | "unknown"; evidence: string }[];
missing_information: ("budget" | "timeline" | "company" | "qualification_criteria")[];
};
// Load these from config so sales ops can change them without a deploy.
const qualification_criteria = {
min_budget: { amount: 20000, currency: "USD" },
countries: ["US", "CA", "GB", "DE"],
criteria: ["Needs at least 50 user seats", "Uses Salesforce or plans to integrate with it"],
};
const routing_rules = [
{ territory: "North America", countries: ["US", "CA"] },
{ territory: "EMEA", countries: ["GB", "DE", "FR"] },
];
export async function qualifyLead(form: {
message: string;
company?: { name?: string; industry?: string; size?: string; country?: string };
budget?: { amount: number; currency: string };
timeline?: string;
}): Promise<LeadResult> {
const res = await fetch("https://kovca.io/api/v1/leads/qualify", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.KOVCA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ ...form, message: form.message.slice(0, 4000), qualification_criteria, routing_rules }),
});
if (!res.ok) throw new Error(`lead qualification failed: ${res.status}`);
return res.json();
}Python
import os, requests
def qualify_lead(form, criteria, routing):
body = {**form, "message": form["message"][:4000],
"qualification_criteria": criteria, "routing_rules": routing}
r = requests.post(
"https://kovca.io/api/v1/leads/qualify",
headers={"Authorization": f"Bearer {os.environ['KOVCA_API_KEY']}"},
json=body, timeout=30,
)
r.raise_for_status()
return r.json()If the call fails, create the lead in your CRM anyway, unassigned and tagged qualification_pending. A lead that waits for a retry is better than one that disappears.
3Route in the CRM
Qualification is decided by the first rule that matches, so it maps directly onto CRM behavior:
| qualification | recommended_action | What happens |
|---|---|---|
hot | prioritize_followup | Assign to the territory owner now and send an alert. Explicit buying intent with a near-term timeline. |
warm | standard_followup | Assign to the territory queue with a normal SLA. |
cold | nurture | Enroll in nurture. Early research or an information request. |
unqualified | nurture | A criterion isn't met, such as a budget below your minimum or a country you don't serve. |
unqualified | manual_review | Not a sales inquiry, or an existing customer. Send to support or customer success, not sales. |
insufficient_information | request_information | Intent is unclear, such as "Interested. Call me." Send a short reply asking what they're looking for. |
territory is the first routing rule whose countries include company.country, or null. Have a fallback owner for null so no lead goes unassigned.
4Alert only for hot leads
Send real-time alerts only for hot. If every warm lead pings a rep, they'll mute the channel within a week. Put warm and cold leads in the CRM views your team already works through, and show criteria_results on the lead record so the rep sees what was met before the first call.
Rolling it out
- Tag, don't route. For two weeks, write
qualificationandcriteria_resultsto the CRM and let the existing process run. Ask reps to flag any lead they disagree with. - Route hot and unqualified first. These are the two ends where routing saves the most time: fast follow-up for buyers, and support requests kept out of sales queues.
- Tune the criteria, not the code. Most disagreements come from a criterion that's worded vaguely. Rewrite it as something a person could check from the message.
Volume and cost
One request per inbound lead. The Free plan covers 1,000 successful requests a month at 10 per minute, which is more than most inbound forms receive. Pay As You Go is $0.005 per successful request at 60 per minute, with a spending cap (pricing). Lead messages aren't stored by Kovca.
Try it on last month's form fills
Take twenty submissions, including the ones sales complained about, and run them through with your real criteria. Sign up free, create a key, and paste one into the playground. The qualification rules are in the Lead Intelligence docs.