Updated
Before you start
- An API key: in Coolie, open Settings → API keys, name the key and click Create key. It is shown once, so keep it in an environment variable (
COOLIE_API_KEYin these examples). It acts as you, with your plan, your credits and your agents. - An agent that watches the signal you filter on: a lead only carries a signal its agent watches.
Filter with curl and jq
curl -s "https://trycoolie.ai/api/v1/leads?limit=100" \
-H "Authorization: Bearer $COOLIE_API_KEY" \
| jq '.leads[] | select(.signal.type == "competitor-churn") | {company: .company.name, change: .signal.headline, date: .signal.postedAt, contact: .contact.name}'Filter in Python, every page
import os
import requests
API = "https://trycoolie.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['COOLIE_API_KEY']}"}
def leads_with_signal(signal_type, since=None, agent_id=None):
"""The leads one signal surfaced, newest first, all pages."""
params = {"limit": 100}
if since:
params["since"] = since
if agent_id:
params["agentId"] = agent_id
while True:
response = requests.get(f"{API}/leads", headers=HEADERS, params=params, timeout=60)
response.raise_for_status()
page = response.json()
for lead in page["leads"]:
if lead["signal"]["type"] == signal_type:
yield lead
if page["nextCursor"] is None:
return
params["cursor"] = page["nextCursor"]
for lead in leads_with_signal("competitor-churn", since="2026-10-01T00:00:00Z"):
print(lead["company"]["name"], "-", lead["signal"]["headline"], "on", lead["signal"]["postedAt"])Pass since to read only the leads found after a moment, and agent_id for one agent's. To wait out the minute's limit instead of failing on a 429, use the get function of the new leads guide.
Filter webhook events as they arrive
Subscribe a webhook to lead.created: each event carries the lead, with its signal, within minutes of an agent finding it.
// In your webhook receiver, once the signature is checked:
const event = JSON.parse(rawBody);
if (event.type === "lead.created" && event.data.lead.signal.type === "competitor-churn") {
const { company, contact, signal } = event.data.lead;
console.log(company.name + ": " + signal.headline + " on " + signal.postedAt);
console.log("Write to " + (contact ? contact.name + ", " + contact.title : "nobody yet"));
}The competitor churn alert template does the same in n8n or Make, and posts to Slack.
Watch competitor churn with an agent
Competitor Churn finds companies that cancelled, downgraded or stopped using a product you name, in the last 90 days; Competitor Renewal finds companies that took it on 9 to 12 months ago and still use it, so a yearly contract is likely to come up for renewal. Each takes up to five products, spelled as GET /api/v1/catalog/competitors lists them; for churn, pick those whose trackChurn is true.
curl https://trycoolie.ai/api/v1/catalog/competitors \
-H "Authorization: Bearer $COOLIE_API_KEY"
curl https://trycoolie.ai/api/v1/agents \
-H "Authorization: Bearer $COOLIE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Teams leaving Acme CRM",
"icp": {
"jobTitles": [
"VP of Sales",
"Head of Sales"
],
"industries": [
"Software Development"
],
"locations": [
"Europe"
],
"companySizes": [
"51-200 employees",
"201-500 employees"
],
"keywordsInclude": [],
"keywordsExclude": [],
"mode": "broad"
},
"signalEventIds": [
"competitor-churn",
"competitor-renewal",
"hiring"
],
"signalConfigs": {
"competitor-churn": {
"competitors": [
"Acme CRM"
]
},
"competitor-renewal": {
"competitors": [
"Acme CRM"
]
},
"hiring": {
"jobTitles": [
"Sales Operations Manager"
]
}
},
"findEmail": true,
"findPhone": false
}'See how to create a Coolie agent with the API for the rest of the body.
What the signal's fields tell
signal.headline: the job posting's title for Hiring, the change for Competitor Churn (for example Stopped using Acme CRM), the new role for Recent Job Change.signal.postedAt: the date of the posting, the change or the move, when it is known.signal.url: where the signal was found, when there is a public page for it.signal.detail: for some job-posting signals, where the buying intent was found in the posting.lookalike: a lead that matches your ICP but came from no signal. Coolie may add some at the end of a quiet day to keep leads flowing; filter them out when you want signal leads only.
A competitor-churn lead
One lead of GET /api/v1/leads whose signal is Competitor Churn. The values are examples.
{
"runId": "ui-7c03aa19",
"prospectId": "p-9e21f4b6c8d0",
"agentId": "agent-3f2a91c0",
"status": "enriched",
"processedAt": "2026-10-19T08:41:55.204Z",
"icpMatch": true,
"aiScore": 9.1,
"feedback": null,
"signal": {
"type": "competitor-churn",
"name": "Competitor Churn",
"headline": "Stopped using Acme CRM",
"detail": null,
"url": null,
"postedAt": "2026-09-28"
},
"company": {
"name": "Halden Freight",
"domain": "halden.example",
"website": "https://halden.example",
"linkedinUrl": "https://www.linkedin.com/company/example",
"industry": "Supply Chain and Logistics",
"employees": 200,
"country": "Germany",
"locations": []
},
"contact": {
"name": "Jonas Weber",
"title": "VP of Sales",
"seniority": "vp",
"linkedinUrl": "https://www.linkedin.com/in/example",
"country": "Germany",
"email": null,
"emailConfidence": null,
"phone": null
},
"research": null,
"sequence": null
}Errors
An error answers with its HTTP status and a body { "error": code, "message": sentence }: branch on the code, show or log the message.
| Status | Code | What to do |
|---|---|---|
| 401 | missing_api_key | No key in the request: send Authorization: Bearer coolie_sk_… or an x-api-key header. |
| 401 | invalid_api_key | Not a Coolie key, or a key that is unknown or revoked: copy it again from Settings → API keys. |
| 400 | invalid_query | since is not a date, cursor is not one the API gave, or limit is not a whole number from 1 to 100. |
| 404 | agent_not_found | No agent with this id in your account. |
| 429 | rate_limited | More than 60 requests in the minute for your account: wait the seconds Retry-After gives, then try again. |
Frequently asked questions
Can the API return one signal's leads only?
Not with a query parameter: filter on signal.type, as above. To narrow the list first, pass agentId for one agent and since for the leads found after a moment.
Can an agent watch competitor churn only?
An agent watches three to five signals, so pair churn with signals that fit the same buyers, such as Competitor Renewal and Hiring, and filter on signal.type.
How recent are competitor-churn leads?
Coolie looks at changes from the last 90 days, and signal.postedAt gives the date of the change.
What is a lookalike lead?
A lead whose signal.type is lookalike: it matches your ICP but came from no signal. Coolie may add some at the end of a quiet day, never in an agent's first two days.
How do I get each churn lead when it is found?
Subscribe a webhook to lead.created and filter on data.lead.signal.type, in your own code or with the Slack alert template.
Every endpoint, field and error is in the API reference, and more guides are in the developer hub.
