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. - Credits: 1 credit a lead researched, charged when its research is saved. The balance must cover the batch.
- Leads with a contact and no research yet (status
enriched), fromGET /api/v1/leads: any other lead is skipped, at no charge.
Queue the research
curl https://trycoolie.ai/api/v1/agents/agent-3f2a91c0/leads/actions \
-H "Authorization: Bearer $COOLIE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "action": "research", "leads": [ { "runId": "ui-5b9e2c41", "prospectId": "p-41b7c2d9e0aa" } ] }'{
"queued": 1,
"skipped": 0
}Asking again for leads still queued resumes them, at no extra charge. A researched lead whose messages are missing gets them written again, free. Leads that cannot be researched are counted in skipped, and cost nothing.
Wait for the result
Subscribe a webhook to lead.researched, which carries the researched lead, or poll the agent's leads until the lead's research and sequence are filled in. A lead keeps its place in the newest-first list, so a recent one is on the first page.
import os
import time
import requests
API = "https://trycoolie.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['COOLIE_API_KEY']}"}
AGENT_ID = "agent-3f2a91c0"
LEAD = {"runId": "ui-5b9e2c41", "prospectId": "p-41b7c2d9e0aa"}
queued = requests.post(f"{API}/agents/{AGENT_ID}/leads/actions", headers=HEADERS,
json={"action": "research", "leads": [LEAD]}, timeout=60)
queued.raise_for_status()
print(queued.json()) # {'queued': 1, 'skipped': 0}
lead = None
while lead is None or lead["research"] is None or lead["sequence"] is None:
time.sleep(30)
page = requests.get(f"{API}/agents/{AGENT_ID}/leads", headers=HEADERS,
params={"limit": 100}, timeout=60).json()
lead = next((item for item in page["leads"] if item["prospectId"] == LEAD["prospectId"]), None)
for insight in lead["research"]["insights"]:
print(insight["id"], insight["type"], "-", insight["insight"])
for message in lead["sequence"]:
print()
print(f"[{message['channel']}, step {message['step']}] {message['subject'] or ''}")
print(message["body"])Write the messages again from your own insight
Add what you know with POST /api/v1/agents/{agentId}/leads/insights (5,000 characters at most, five a lead, free): it answers 201 with its id, c1 for the first. Then POST /api/v1/agents/{agentId}/leads/sequence with the insight ids to write from: the new messages replace the lead's, free, and the request waits for them, up to a minute.
curl https://trycoolie.ai/api/v1/agents/agent-3f2a91c0/leads/insights \
-H "Authorization: Bearer $COOLIE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "runId": "ui-5b9e2c41", "prospectId": "p-41b7c2d9e0aa", "text": "Met Camille at a trade show: their two sites still plan shifts by hand." }'
curl https://trycoolie.ai/api/v1/agents/agent-3f2a91c0/leads/sequence \
-H "Authorization: Bearer $COOLIE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "runId": "ui-5b9e2c41", "prospectId": "p-41b7c2d9e0aa", "insightIds": ["c1", "s1", "w1"] }'What the research holds
companyContextandroleTension: the company in a few lines, and the problem the contact's role points at;confidencehigh, medium or low.insights: the agent's, best first (s1the buying signal,w1… the web,p1… the lead's posts), then yours (c1…,customtrue), each with its date, why it matters, a score out of 10 and its sources.persona: the pain point, the current solution, the risks, the differentiation and the benefits.sequence: the messages in order, each with its channel, its step, a subject for emails, and its body.
Researching sends nothing: messages go out only from a campaign you launch.
A researched lead
The lead as GET /api/v1/leads gives it once researched. The values are examples.
{
"runId": "ui-5b9e2c41",
"prospectId": "p-41b7c2d9e0aa",
"agentId": "agent-3f2a91c0",
"status": "written",
"processedAt": "2026-10-19T09:02:13.511Z",
"icpMatch": true,
"aiScore": 8.4,
"feedback": null,
"signal": {
"type": "hiring",
"name": "Hiring",
"headline": "Operations Manager",
"detail": null,
"url": "https://jobs.example.com/12345",
"postedAt": "2026-10-17"
},
"company": {
"name": "Northwind Health",
"domain": "northwind.example",
"website": "https://northwind.example",
"linkedinUrl": "https://www.linkedin.com/company/example",
"industry": "Hospitals and Health Care",
"employees": 320,
"country": "France",
"locations": [
"Lyon, France"
]
},
"contact": {
"name": "Camille Martin",
"title": "COO",
"seniority": "c_suite",
"linkedinUrl": "https://www.linkedin.com/in/example",
"country": "France",
"email": "camille.martin@northwind.example",
"emailConfidence": 0.92,
"phone": null
},
"research": {
"companyContext": "Northwind Health runs two clinics around Lyon and is hiring an Operations Manager to run scheduling across both sites.",
"roleTension": "As COO, Camille owns the clinics' scheduling while the team grows faster than its tools.",
"confidence": "high",
"insights": [
{
"id": "s1",
"custom": false,
"type": "Hiring Activity",
"insight": "Northwind Health is hiring: Operations Manager",
"date": "2026-10-17",
"relevancy": "The buying signal that brought this lead in.",
"score": null,
"sources": [
"https://jobs.example.com/12345"
],
"hiddenProblem": null
},
{
"id": "w1",
"custom": false,
"type": "Company News",
"insight": "Northwind Health opened its second clinic in September.",
"date": "2026-09-12",
"relevancy": "A second site makes shift planning by hand harder.",
"score": 8,
"sources": [
"https://news.example.com/northwind-second-clinic"
],
"hiddenProblem": "Schedules kept in spreadsheets per site."
}
],
"persona": {
"painPoint": "Planning shifts across two clinics by hand.",
"currentSolution": "Spreadsheets per site.",
"risks": "Gaps in cover as the team grows.",
"differentiation": "One schedule for every site.",
"benefits": "Hours back each week for the operations team."
}
},
"sequence": [
{
"channel": "email",
"step": 1,
"subject": "Two clinics, one schedule",
"body": "Hi Camille, congratulations on opening the second clinic. …"
},
{
"channel": "linkedin",
"step": 2,
"subject": null,
"body": "Hi Camille, I wrote to you about scheduling across your two sites. …"
}
]
}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 |
|---|---|---|
| 400 | invalid_body | action is not research or delete, or leads is empty or holds more than 50. |
| 402 | no_credits | The balance does not cover the batch; the answer says needed and balance. |
| 404 | agent_not_found | No agent with this id in your account. |
| 409 | not_researched | Insights and sequences need a researched lead: research it first. |
| 409 | researching | The lead's research or writing is under way: try again once it is done. |
| 400 | insight_too_long | An insight holds 5,000 characters at most. |
| 400 | too_many_insights | A lead holds five insights of yours at most. |
| 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. |
| 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
How much does researching a lead cost?
1 credit a lead researched, charged when its research is saved. Adding your own insights and writing the messages again are free.
How long does the research take?
A minute or two a lead, in the background: the call answers at once with how many leads it queued.
Which leads can be researched?
Leads with a contact and no research yet (status enriched), and researched leads whose messages are missing, which are written again for free. The others are counted in skipped, at no charge.
Does researching a lead send anything?
No. It writes the messages; nothing goes out until the lead is in a campaign you launch.
Can I get the result by webhook?
Yes: subscribe a webhook to lead.researched. The event carries the lead with its research and its sequence.
Every endpoint, field and error is in the API reference, and more guides are in the developer hub.
