Skip to main content
← Resources
Tutorial

Build a GTM Agent with ABM.dev

A complete, copy-paste-ready guide to building an AI agent that does account-based marketing — enriching contacts, researching LinkedIn profiles, and sending personalised outreach — all powered by ABM.dev APIs.

1. Introduction

ABM.dev provides a suite of APIs that let AI agents do account-based marketing autonomously. Instead of manually researching prospects, toggling between tabs, and copy-pasting data, your agent can call ABM.dev to enrich contacts with data from 10+ sources, research people on LinkedIn, and send personalised outreach messages — all in a single agentic loop.

This tutorial walks you through building a GTM (go-to-market) agent from scratch. By the end you will have a working agent that takes a target company, finds key contacts, enriches them, researches their LinkedIn profiles, and drafts personalised outreach.

2. What You'll Build

Your GTM agent will:

  • Take a target company name as input
  • Enrich key contacts at that company with verified emails, phone numbers, and company data
  • Research their LinkedIn profiles for experience, skills, and mutual connections
  • Generate personalised outreach messages based on enriched data
  • Send messages via LinkedIn through your team's closest connection

The agent uses a tool-calling loop: you provide tool definitions, the LLM decides which tools to call and in what order, and your handler code executes the actual API requests against ABM.dev.

3. Prerequisites

# Install the SDKs you'll need
pip install anthropic requests
# or, for OpenAI:
pip install openai requests

4. Tool Definitions

AI agents need tool definitions that describe the available actions, their parameters, and what they return. Below are the three core tools your GTM agent needs, formatted for three popular frameworks.

Claude (Anthropic SDK) — Python

tool_definitions.py

import anthropic

tools = [
    {
        "name": "enrich_contact",
        "description": "Enrich a contact with data from 10+ sources. Returns verified emails, phone numbers, company data, and AI-generated insights.",
        "input_schema": {
            "type": "object",
            "properties": {
                "email": {"type": "string", "description": "Contact's email address"},
                "linkedin_url": {"type": "string", "description": "Contact's LinkedIn profile URL"},
            },
            "required": ["email"]
        }
    },
    {
        "name": "linkedin_research",
        "description": "Research a person's LinkedIn profile. Returns experience, education, skills, and contact info.",
        "input_schema": {
            "type": "object",
            "properties": {
                "public_id": {"type": "string", "description": "LinkedIn public ID (from profile URL)"},
            },
            "required": ["public_id"]
        }
    },
    {
        "name": "linkedin_message",
        "description": "Send a personalised LinkedIn message to a prospect via your team's closest connection.",
        "input_schema": {
            "type": "object",
            "properties": {
                "recipient": {"type": "string", "description": "LinkedIn public ID of the recipient"},
                "text": {"type": "string", "description": "Message text (max 8000 chars)"},
            },
            "required": ["recipient", "text"]
        }
    },
]

OpenAI Function Calling — Python

openai_tools.py

import openai

tools = [
    {
        "type": "function",
        "function": {
            "name": "enrich_contact",
            "description": "Enrich a contact with data from 10+ sources. Returns verified emails, phone numbers, company data, and AI-generated insights.",
            "parameters": {
                "type": "object",
                "properties": {
                    "email": {"type": "string", "description": "Contact's email address"},
                    "linkedin_url": {"type": "string", "description": "Contact's LinkedIn profile URL"},
                },
                "required": ["email"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "linkedin_research",
            "description": "Research a person's LinkedIn profile. Returns experience, education, skills, and contact info.",
            "parameters": {
                "type": "object",
                "properties": {
                    "public_id": {"type": "string", "description": "LinkedIn public ID (from profile URL)"},
                },
                "required": ["public_id"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "linkedin_message",
            "description": "Send a personalised LinkedIn message to a prospect via your team's closest connection.",
            "parameters": {
                "type": "object",
                "properties": {
                    "recipient": {"type": "string", "description": "LinkedIn public ID of the recipient"},
                    "text": {"type": "string", "description": "Message text (max 8000 chars)"},
                },
                "required": ["recipient", "text"]
            }
        }
    },
]

LangChain — Python

langchain_tools.py

from langchain.tools import tool
import requests

ABM_API_KEY = "your_api_key"
ABM_BASE_URL = "https://api.abm.dev"
HEADERS = {
    "Authorization": f"Bearer {ABM_API_KEY}",
    "Content-Type": "application/json",
}


@tool
def enrich_contact(email: str, linkedin_url: str = None):
    """Enrich a contact with data from 10+ sources.
    Returns verified emails, phone numbers, company data, and AI-generated insights."""
    response = requests.post(
        f"{ABM_BASE_URL}/v1/enrichments",
        headers=HEADERS,
        json={"email": email, "linkedinUrl": linkedin_url},
    )
    return response.json()


@tool
def linkedin_research(public_id: str):
    """Research a person's LinkedIn profile.
    Returns experience, education, skills, and contact info."""
    response = requests.get(
        f"{ABM_BASE_URL}/v1/linkedin/profile/{public_id}",
        headers=HEADERS,
    )
    return response.json()


@tool
def linkedin_message(recipient: str, text: str):
    """Send a personalised LinkedIn message to a prospect
    via your team's closest connection."""
    response = requests.post(
        f"{ABM_BASE_URL}/v1/linkedin/messages",
        headers=HEADERS,
        json={"recipient": recipient, "text": text},
    )
    return response.json()


# Bind tools to your LLM:
# from langchain_anthropic import ChatAnthropic
# llm = ChatAnthropic(model="claude-sonnet-4-20250514")
# llm_with_tools = llm.bind_tools([enrich_contact, linkedin_research, linkedin_message])

5. Implementing the Tool Handlers

Each tool definition needs a handler that makes the actual HTTP request to ABM.dev. Here are the implementations:

handlers.py

import requests

ABM_API_KEY = "your_api_key"
ABM_BASE_URL = "https://api.abm.dev"
HEADERS = {
    "Authorization": f"Bearer {ABM_API_KEY}",
    "Content-Type": "application/json",
}


def enrich_contact(email: str, linkedin_url: str = None):
    """Call the ABM.dev enrichment endpoint."""
    response = requests.post(
        f"{ABM_BASE_URL}/v1/enrichments",
        headers=HEADERS,
        json={"email": email, "linkedinUrl": linkedin_url},
    )
    return response.json()


def linkedin_research(public_id: str):
    """Fetch a LinkedIn profile via ABM.dev."""
    response = requests.get(
        f"{ABM_BASE_URL}/v1/linkedin/profile/{public_id}",
        headers=HEADERS,
    )
    return response.json()


def linkedin_message(recipient: str, text: str):
    """Send a LinkedIn message via ABM.dev."""
    response = requests.post(
        f"{ABM_BASE_URL}/v1/linkedin/messages",
        headers=HEADERS,
        json={"recipient": recipient, "text": text},
    )
    return response.json()


# Dispatcher — maps tool names to handler functions
TOOL_HANDLERS = {
    "enrich_contact": enrich_contact,
    "linkedin_research": linkedin_research,
    "linkedin_message": linkedin_message,
}


def handle_tool_call(name: str, inputs: dict):
    """Route a tool call to the correct handler."""
    handler = TOOL_HANDLERS.get(name)
    if handler is None:
        raise ValueError(f"Unknown tool: {name}")  
    return handler(**inputs)

6. The Agent Loop

Now wire everything together into a complete agentic loop. The agent sends a prompt to Claude, Claude decides which tools to call, your code executes the calls, and the results are fed back until the agent has a final answer.

agent.py

import json
import anthropic
from handlers import handle_tool_call
from tool_definitions import tools

client = anthropic.Anthropic()


def run_gtm_agent(company: str):
    """Run the GTM agent against a target company."""
    messages = [
        {
            "role": "user",
            "content": (
                f"Find decision-makers at {company}, enrich their profiles, "
                f"research them on LinkedIn, and draft personalised "
                f"outreach messages."
            )
        }
    ]

    while True:
        response = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=4096,
            tools=tools,
            messages=messages,
        )

        # If the model wants to use tools, execute them
        if response.stop_reason == "tool_use":
            tool_results = []
            for block in response.content:
                if block.type == "tool_use":
                    result = handle_tool_call(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": json.dumps(result),
                    })  

            # Feed tool results back to the model
            messages.append({"role": "assistant", "content": response.content})
            messages.append({"role": "user", "content": tool_results})
        else:
            # Model is done — return its final text response
            return response.content[0].text


# Run the agent
if __name__ == "__main__":
    result = run_gtm_agent("Acme Corp")
    print(result)

That's it. Run python agent.py and the agent will orchestrate calls to ABM.dev, enriching contacts and drafting outreach autonomously. You can watch the tool calls in real-time by adding print(block.name, block.input) inside the loop.

7. Next Steps

Now that you have a working GTM agent, here are some ways to extend it:

  • People Finder — use the People Finder API to discover contacts at a company by role, seniority, or department (coming soon)
  • Webhook notifications — set up webhooks so your agent gets notified when async enrichments complete, instead of polling
  • Batch enrichment — upload a CSV of contacts and enrich them all at once, then iterate over results
  • Full API reference — explore every endpoint in the API Reference

Questions or feedback? Reach us at support@abm.dev