Q4 Promo: $39/mo $85 for 3 months with code Q4DEAL · See pricing →

Build an Account Research Agent with Python

Tutorial: get a free Salesmotion API key, resolve companies, pull insights, earnings and news, and build a Python agent that writes daily research briefs.

Semir Jahic··9 min read
Build an Account Research Agent with Python

Most "account research" still means a sales rep toggling between six browser tabs, copying LinkedIn bios into a Google Doc, and hoping they noticed the earnings call from last week. It is slow, inconsistent, and does not scale.

What if a short Python script could do it every morning before you open your laptop?

In this tutorial, you will build a real-time account research agent that watches a list of target accounts, pulls intelligence from the Salesmotion API, and generates a structured research brief, optionally delivered to Slack. By the end, you will have a working script you can schedule with cron and forget about.

Key Takeaways

  • The Salesmotion API gives you structured company intelligence (profile, AI insights, earnings calls, news) over REST. The free tier includes 100 credits a month with no credit card.
  • You can build a working account research agent in well under 100 lines of Python using requests and an LLM SDK.
  • Combining company insights, earnings calls and news into one brief gives reps the context they need before a call.
  • The same pattern extends to scoring, outreach triggers, CRM enrichment, or any workflow where fresh account context matters.

Prerequisites

Before you start, make sure you have:

  • Python 3.10+ installed
  • A free Salesmotion API key: sign up here (100 credits a month, no credit card required)
  • An Anthropic API key for the LLM-powered brief generation (optional: you can skip Step 4 and use raw data instead)
  • A Slack webhook URL (optional, for push notifications)

Install the two Python dependencies:

pip install requests anthropic
Andrew Giordano
“The Business Development team gets 80 to 90 percent of what they need in 15 minutes. That is a complete shift in how our reps work.”

Andrew Giordano

VP of Global Commercial Operations, Analytic Partners

Read case study →

Step 1: Get Your Free API Key

Create a free account on the API sign-up page. You get an API key immediately: no sales call, no credit card.

The free tier gives you 100 credits per month, and only successful calls consume credits. The agent below makes three calls per account per run (plus one resolve call the first time it sees a company), so the free tier covers about one account a day, which is plenty for building and testing. For production volume, the Build plan is $99 a month for 2,500 credits.

Save your API key somewhere safe. Every request sends it as a bearer token.

Step 2: Resolve a Company and Read Its Insights

The public API works resolve-first: you look a company up by domain, stock symbol, website or LinkedIn URL, get back its canonical id, and use that id on every company endpoint. The full reference is in the API docs.

curl:

curl -s https://api.salesmotion.io/v1/companies/by-input/snowflake.com \
  -H "Authorization: Bearer YOUR_API_KEY" | python -m json.tool

Python:

import requests

API_KEY = "your_api_key_here"
BASE_URL = "https://api.salesmotion.io/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

def resolve_company(company_input: str) -> str:
    """Return the canonical company id for a domain, ticker, or URL."""
    r = requests.get(f"{BASE_URL}/companies/by-input/{company_input}", headers=HEADERS)
    r.raise_for_status()
    return r.json()["data"]["id"]

def get_company(company_id: str) -> dict:
    """Company profile plus AI-generated insights (insights are opt-in)."""
    r = requests.get(
        f"{BASE_URL}/companies/{company_id}",
        headers=HEADERS,
        params={"onlyWithFields": "id,name,domain,description,industry,numberOfEmployees,insights"},
    )
    r.raise_for_status()
    return r.json()["data"]

company_id = resolve_company("snowflake.com")
print(get_company(company_id))

Response shape (abridged; field names from the API reference):

{
  "data": {
    "id": "...",
    "name": "...",
    "domain": "...",
    "description": "...",
    "industry": "...",
    "numberOfEmployees": 0,
    "insights": { }
  },
  "meta": { "requestId": "...", "apiVersion": "...", "timestamp": "..." },
  "usage": { "limit": 100, "used": 3, "remaining": 97, "resetsAt": "..." }
}

Every response carries a usage block, so the script always knows how many credits remain.

Step 3: Pull Earnings Calls and News for Deeper Context

The insights give you a quick overview, but sales conversations often hinge on specifics: what the CEO said on the last earnings call, or a partnership announced last week. Two company sub-resources fill that gap.

Earnings calls

Transcripts and AI analysis are opt-in, so ask for the fields you need:

def get_earnings(company_id: str) -> list[dict]:
    r = requests.get(
        f"{BASE_URL}/companies/{company_id}/earnings-calls",
        headers=HEADERS,
        params={"perPage": 1, "onlyWithFields": "id,year,quarter,happenedAt,generatedAnalysis"},
    )
    r.raise_for_status()
    return r.json()["data"]

Each item carries year, quarter, happenedAt, public links such as reportUrl, and, when requested, generatedAnalysis and fullTranscript.

News and press releases

def get_news(company_id: str) -> list[dict]:
    r = requests.get(
        f"{BASE_URL}/companies/{company_id}/news-articles",
        headers=HEADERS,
        params={"perPage": 5},
    )
    r.raise_for_status()
    return r.json()["data"]

Each article includes headline, url, datePublished, publisher and a short description; full article text is opt-in with onlyWithFields=articleBody.

Step 4: Generate a Research Brief with an LLM

Now the interesting part. You have structured data from three endpoints: company insights, earnings calls and news. Instead of dumping raw JSON into Slack, feed it to an LLM and get a concise, action-oriented brief.

import anthropic
import json

client = anthropic.Anthropic()  # Uses ANTHROPIC_API_KEY env var

def generate_brief(company: dict, earnings: list, news: list) -> str:
    context = json.dumps(
        {"company": company, "earnings": earnings, "news": news},
        indent=2,
    )

    message = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=[
            {
                "role": "user",
                "content": f"""You are a sales research analyst. Given the following account
intelligence data, write a concise daily research brief (max 300 words).

Structure:
1. Company snapshot (one sentence)
2. Key developments (bullet points: what changed recently)
3. Potential talking points for a sales conversation
4. Risk flags (if any)

Account data:
{context}""",
            }
        ],
    )
    return message.content[0].text

The prompt is deliberately structured: snapshot, developments, talking points, risks. This mirrors what a good AE actually needs before a call: not a wall of text, but a scannable brief they can read in 30 seconds.

Step 5: Monitor Multiple Accounts

A single account is useful for testing. In production, you want to run this across your target list every morning. Key the list by domain and cache the resolved ids so you only pay for the resolve call once.

TARGET_DOMAINS = ["snowflake.com", "datadoghq.com", "okta.com"]
_company_ids: dict[str, str] = {}

def run_daily_research() -> list[dict]:
    briefs = []
    for domain in TARGET_DOMAINS:
        try:
            company_id = _company_ids.get(domain) or resolve_company(domain)
            _company_ids[domain] = company_id
            company = get_company(company_id)
            earnings = get_earnings(company_id)
            news = get_news(company_id)
            briefs.append({
                "company": company.get("name", domain),
                "brief": generate_brief(company, earnings, news),
            })
            print(f"Done: {company.get('name', domain)}")
        except Exception as e:
            print(f"Error {domain}: {e}")
    return briefs

Three accounts at three calls each is 9 credits per run. On the free tier that is enough to test the full loop for a few days; on a paid plan you can run it daily across your whole target list.

Step 6: Send to Slack (Optional)

The final piece: push the briefs to a Slack channel so your team sees them without logging in anywhere.

import requests as req

SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

def send_to_slack(briefs: list[dict]) -> None:
    for entry in briefs:
        payload = {
            "text": f"*📊 Daily Brief: {entry['company']}*\n\n{entry['brief']}",
        }
        resp = req.post(SLACK_WEBHOOK, json=payload)
        if resp.status_code != 200:
            print(f"Slack error for {entry['company']}: {resp.text}")

Create a Slack incoming webhook at api.slack.com/messaging/webhooks. Point it at your team's research or deal-room channel.

Full Script

Here is the complete agent, combining every step above into a single file:

#!/usr/bin/env python3
"""Daily account research agent: Salesmotion API + Claude."""

import json
import os
import requests
import anthropic

# --- Config ---
API_KEY = os.environ["SALESMOTION_API_KEY"]
BASE_URL = "https://api.salesmotion.io/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
SLACK_WEBHOOK = os.environ.get("SLACK_WEBHOOK_URL")
TARGET_DOMAINS = ["snowflake.com", "datadoghq.com", "okta.com"]

client = anthropic.Anthropic()

# --- API helpers ---
def resolve_company(company_input: str) -> str:
    r = requests.get(f"{BASE_URL}/companies/by-input/{company_input}", headers=HEADERS)
    r.raise_for_status()
    return r.json()["data"]["id"]

def get_company(company_id: str) -> dict:
    r = requests.get(f"{BASE_URL}/companies/{company_id}", headers=HEADERS,
                     params={"onlyWithFields": "id,name,domain,description,industry,insights"})
    r.raise_for_status()
    return r.json()["data"]

def get_earnings(company_id: str) -> list:
    r = requests.get(f"{BASE_URL}/companies/{company_id}/earnings-calls", headers=HEADERS,
                     params={"perPage": 1, "onlyWithFields": "id,year,quarter,happenedAt,generatedAnalysis"})
    r.raise_for_status()
    return r.json()["data"]

def get_news(company_id: str) -> list:
    r = requests.get(f"{BASE_URL}/companies/{company_id}/news-articles", headers=HEADERS,
                     params={"perPage": 5})
    r.raise_for_status()
    return r.json()["data"]

# --- LLM brief ---
def generate_brief(company: dict, earnings: list, news: list) -> str:
    context = json.dumps({"company": company, "earnings": earnings, "news": news}, indent=2)
    message = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=[{
            "role": "user",
            "content": f"""You are a sales research analyst. Given the following account
intelligence data, write a concise daily brief (max 300 words).

Structure:
1. Company snapshot (one sentence)
2. Key developments (bullet points)
3. Talking points for a sales conversation
4. Risk flags (if any)

Account data:
{context}""",
        }],
    )
    return message.content[0].text

# --- Slack ---
def send_to_slack(briefs: list[dict]) -> None:
    if not SLACK_WEBHOOK:
        return
    for entry in briefs:
        requests.post(SLACK_WEBHOOK, json={
            "text": f"*Daily Brief: {entry['company']}*\n\n{entry['brief']}",
        })

# --- Main ---
def main():
    briefs = []
    for domain in TARGET_DOMAINS:
        try:
            company_id = resolve_company(domain)
            company = get_company(company_id)
            briefs.append({
                "company": company.get("name", domain),
                "brief": generate_brief(company, get_earnings(company_id), get_news(company_id)),
            })
            print(f"Done: {company.get('name', domain)}")
        except Exception as e:
            print(f"Error {domain}: {e}")

    send_to_slack(briefs)
    print(f"\nGenerated {len(briefs)} briefs.")

if __name__ == "__main__":
    main()

Save it as research_agent.py, set your environment variables, and run:

export SALESMOTION_API_KEY="your_key_here"
export ANTHROPIC_API_KEY="your_key_here"
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."  # optional

python research_agent.py

To run it every morning at 7 AM, add a cron entry:

crontab -e
# Add this line:
0 7 * * * cd /path/to/agent && python research_agent.py

What to Build Next

This agent is a starting point. Here are a few ways to extend it:

  • Add more endpoints. The public API also covers job openings and SEC filings. Adding the job-openings endpoint, for example, lets you detect hiring surges that signal budget expansion.
  • Build account scoring. Use the signals and firmographic data to compute a priority score. Surface the accounts that changed the most since yesterday.
  • Trigger outreach automatically. When a high-priority signal fires (new CTO, earnings beat, funding round), use the brief as context for a personalized email draft.
  • Connect to your CRM. Push the briefs and scores into Salesforce or HubSpot so reps see fresh context without leaving their workflow.
  • Use the MCP server instead. If your team already uses Claude or another AI assistant, the Salesmotion MCP server gives you the same intelligence through natural conversation, no code required.

The pattern is always the same: pull structured data from the API, add your own logic, and push the output where your team already works. If you want to go deeper on the category, see our guide to AI-powered account research, the honest take on building account intelligence with Claude Code, or the DIY account intelligence trap.

Get your free API key and start building.

Frequently Asked Questions

How many API credits does this agent use per run?

Three endpoints per account per run (company insights, earnings, news) = 3 credits, plus 1 credit the first time you resolve a company. If you monitor 10 accounts daily, that is 30 credits per day, or about 900 per month. The free tier's 100 credits a month cover about one account a day. The Build plan at $99/month gives you 2,500 credits, which covers roughly 27 accounts daily.

Can I use a different LLM instead of Claude?

Yes. The LLM step is completely independent of the Salesmotion API. You can swap in OpenAI, Gemini, Mistral, or any model that accepts a text prompt. The Salesmotion API returns structured JSON, so any LLM can process it. The Anthropic SDK is used here because Claude handles structured data particularly well.

Do I need a paid Salesmotion plan to follow this tutorial?

No. The free tier gives you 100 credits per month with no credit card required. That is enough to follow every step in this tutorial and monitor a few accounts daily. Scale up to a paid plan when you need more volume.

What is the difference between the API and the Salesmotion platform?

The API gives you raw, structured intelligence via REST endpoints, ideal for developers building custom tools, agents, or data pipelines. The Salesmotion platform is the full product: dashboard, AI agents, CRM integrations, Slack alerts, and the MCP server for AI assistants. Ask about API access on Team and Enterprise platform plans when you book a demo.

About the Author

Semir Jahic
Semir Jahic

CEO & Co-Founder at Salesmotion

Semir is the CEO and Co-Founder of Salesmotion, a B2B account intelligence platform that helps sales teams research accounts in minutes instead of hours. With deep experience in enterprise sales and revenue operations, he writes about sales intelligence, account-based selling, and the future of B2B go-to-market.

Follow on LinkedIn

Related articles

Ready to transform your account research?

See how Salesmotion helps sales teams save hours on every account.

Book a demo