Developer guide

How to filter Coolie leads by buying signal with the API

Every Coolie lead names the signal that surfaced it in signal.type: hiring, funding, recent-job-change, tech-used, competitor-churn, competitor-renewal and the others GET /api/v1/signals lists. The leads list has no signal parameter, so read GET /api/v1/leads (agentId narrows it to one agent) and keep the leads whose signal.type is the one you want, or filter lead.created webhook events the same way to act on each lead as it arrives.

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_KEY in 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 + 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

Python
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.

JavaScript
// 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
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.

JSON
{
  "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.

StatusCodeWhat to do
401missing_api_keyNo key in the request: send Authorization: Bearer coolie_sk_… or an x-api-key header.
401invalid_api_keyNot a Coolie key, or a key that is unknown or revoked: copy it again from Settings → API keys.
400invalid_querysince is not a date, cursor is not one the API gave, or limit is not a whole number from 1 to 100.
404agent_not_foundNo agent with this id in your account.
429rate_limitedMore 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.

Your next conversation is already happening somewhere.

Set up an agent in five minutes. The first leads arrive with their reason to write.