Integrations · LangGraph (LangChain)

LangGraph Enrichment — RichAPI as a Tool Node

Wire RichAPI into a LangGraph agent two ways: hand-written @tool functions over the REST API, or the hosted MCP server loaded through langchain-mcp-adapters. Waterfall misses on email_finder, email_verifier and phone_finder cost zero credits, which is what makes a retry loop safe to build.

Start free — 25 credits

Last updated September 22, 2026

Connect it.

  1. 01

    Create a RichAPI account at app.richapi.ai/signup, copy your key, and export it as RICHAPI_KEY. 25 free credits, no card.

  2. 02

    Option A, explicit tools: write one @tool per endpoint wrapping a POST to https://api.richapi.ai/api/v1/{endpoint} with an x-api-key header, and give each a docstring the model will actually read.

  3. 03

    Option B, MCP: pip install langchain-mcp-adapters, point MultiServerMCPClient at https://mcp.richapi.ai/mcp with the x-api-key header, and load the whole catalog as tools in one call.

  4. 04

    Bind the tools to your model, add a ToolNode, and route with tools_condition — the standard LangGraph agent loop, no custom executor needed.

  5. 05

    Return the full response body to the model, including success, billed and execution_log, so the agent can decide whether a miss is worth retrying.

  6. 06

    Cap it. A recursion_limit plus a per-run credit counter in state is the difference between a bug and an invoice.

What you can ask for.

Copy, paste, edit the brackets, run.

Account research graph: enrich_company on a LinkedIn URL → people_search for the buying committee → email_finder per person → verifier on anything risky.
Conditional edge that routes to a personal-email branch only when the work email lookup returns success false.
Human-in-the-loop interrupt before any phone_finder call, because it is the most expensive endpoint in the catalog.
Map-reduce over 50 domains with Send(), one enrich_company per branch, results reduced into a single scored table.
Checkpointed graph that resumes a half-finished list build without re-paying for the rows it already enriched.

Why it matters

Agents retry. On most enrichment APIs an agent that retries a miss three times pays three times for nothing; on the waterfall endpoints the retry is free, and the execution_log tells the agent whether retrying could ever have helped.

LangGraph enrichment: RichAPI as a tool node

Two ways in. Write the tools yourself against the REST API, or load the hosted MCP server and get the catalog for free. Both end at the same `ToolNode`, on the same key and the same credit pool.

Option A — explicit `@tool` functions

Pick this when you want the model to see three tools instead of the whole catalog, and when you want to normalise the response before it reaches the context window. ```python import os, httpx from langchain_core.tools import tool BASE = "https://api.richapi.ai/api/v1" HEADERS = {"x-api-key": os.environ["RICHAPI_KEY"]} def _post(endpoint: str, payload: dict) -> dict: r = httpx.post(f"{BASE}/{endpoint}", json=payload, headers=HEADERS, timeout=None) r.raise_for_status() # non-2xx never bills; let it raise return r.json() @tool def find_work_email(first_name: str, last_name: str, company_domain: str) -> dict: """Find a verified work email. Returns success, the email when found, billed, and execution_log listing each provider attempt. A miss returns success=False and billed=False and costs nothing.""" return _post("email_finder", { "first_name": first_name, "last_name": last_name, "company_domain": company_domain }) @tool def enrich_company_by_linkedin(linkedin_url: str) -> dict: """Company data from a LinkedIn company URL: size, industry, specialties. Takes a LinkedIn URL, NOT a domain. Bills on any 2xx, empty result included.""" return _post("enrich_company", {"linkedin_url": linkedin_url}) ``` The docstrings are the integration. A model that does not know `enrich_company` wants a LinkedIn URL will confidently hand it a domain, and you will pay for the 2xx that comes back thin. Say it in the docstring, in capitals, and say what a miss costs — a model that knows retries are free on `email_finder` and expensive on `enrich_profile` makes better decisions than one guessing. Leave `timeout=None`. Enrichment waterfalls take as long as the providers take, and an aggressive client timeout turns a call you were not charged for into a call you cannot see the result of.

Option B — the hosted MCP server

Pick this when you want the whole catalog and you do not want to maintain wrappers as endpoints get added. ```python from langchain_mcp_adapters.client import MultiServerMCPClient client = MultiServerMCPClient({ "richapi": { "url": "https://mcp.richapi.ai/mcp", "transport": "streamable_http", "headers": {"x-api-key": os.environ["RICHAPI_KEY"]}, } }) tools = await client.get_tools() ``` The catalog is served from live endpoint config rather than baked into a release, so new endpoints appear in `tools` without a version bump on your side. The tradeoff is the whole catalog's tool descriptions in the prompt; if that hurts, filter the list before binding.

The graph

Standard prebuilt loop, nothing exotic: ```python from langgraph.graph import StateGraph, MessagesState, START from langgraph.prebuilt import ToolNode, tools_condition tools = [find_work_email, enrich_company_by_linkedin] model = ChatAnthropic(model="claude-sonnet-4-5").bind_tools(tools) def call_model(state: MessagesState): return {"messages": [model.invoke(state["messages"])]} g = StateGraph(MessagesState) g.add_node("model", call_model) g.add_node("tools", ToolNode(tools)) g.add_edge(START, "model") g.add_conditional_edges("model", tools_condition) g.add_edge("tools", "model") graph = g.compile() graph.invoke( {"messages": [("user", "Find a work email for Jane Doe at acme.com.")]}, {"recursion_limit": 12}, ) ``` Set `recursion_limit`. An agent that discovers it can call an enrichment endpoint in a loop will do exactly that, and the endpoints that bill on every 2xx do not care that it was an accident.

What the agent should do with a miss

The reason to return the whole body rather than just the email: `execution_log` shows who ran and what each returned. An agent reading that can tell a real miss from a call worth retrying. We do not publish the provider roster; it changes as providers are added and dropped. The log is what you get instead, and it is per-call and true. Add a conditional edge that routes on `success == False` from `find_work_email` to a personal-email branch rather than back to the model, which otherwise tends to retry the identical arguments.

Cost control in state

| Endpoint | Cost | Retry on a miss | | --- | --- | --- | | `email_finder` | `5 credits` | Free | | `email_verifier` | `2 credits` | Free | | `phone_finder` | `25 credits` | Free | | `enrich_company` | `1 credit` | Charged every time | | `enrich_profile` | `1 credit` | Charged every time | | `people_search` | `0.1 credits per result` | Charged every time | Carry a `credits_spent` int in your state schema, add to it on each tool return, and put a conditional edge in front of `phone_finder` that hits an `interrupt()` above a threshold. Human-in-the-loop on the 25-credit endpoint is cheap insurance; on a 1-credit lookup it is theatre. Rates are on [pricing](/pricing).

Guardrails you get for free

Every RichAPI endpoint is a read-only lookup. Nothing in the catalog sends email, posts anything, or writes to a CRM, so a badly-planned graph spends credits rather than mailing a prospect a template with `{{first_name}}` still in it. That is a real property of the tool surface, and it is why an autonomous loop here is a more reasonable thing to build than one holding a send button.

Where we stop with LangGraph

Data layer only — no prospecting UI, no sequencer, no CRM app. For the same tools in a chat client instead of a graph, see [Claude](/integrations/claude) or [Cursor](/integrations/cursor), with the rest of the family under [integrations](/integrations). For the non-agentic version of the same pipeline, [agent-driven account research](/use-cases/agent-driven-account-research) and [list build and verify](/use-cases/list-build-and-verify).

LangGraph FAQ

**REST tools or MCP?** MCP to explore, hand-written tools for anything you run on a schedule. Fewer tools, tighter docstrings, better behaviour. **Does streaming work?** The graph streams normally; the tool calls are plain blocking HTTP inside a node. **Can I run tools in parallel?** Yes — `Send()` for map-reduce fan-out works fine, subject to whatever your account's rate limits are. Rate-limited responses are non-2xx and do not bill. **How do I stop the agent burning credits in a loop?** `recursion_limit`, a credit counter in state, and an `interrupt()` before `phone_finder`. In that order. **Does a checkpointer help?** Yes, and it is the underrated one. Resuming a checkpointed graph skips the rows you already paid to enrich. **Same key as my REST code?** Yes, one key, one pool. Issue separate keys per graph for per-key usage attribution.

Start with LangGraph

25 free credits, no card. Paste the `find_work_email` tool above into an existing graph and run one query — it is a five-minute test and the miss costs nothing. Endpoint reference: [email finder](/api/email-finder), [person enrichment](/api/person-enrichment), full list under [platform](/platform).

Frequently asked.

Does LangGraph (LangChain) see my RichAPI key?
No — LangGraph Enrichment — RichAPI as a Tool Node authenticates through the connection you set up, using the same key and credit pool as the REST API. Revoking access from your RichAPI dashboard disconnects it immediately.
Does this cost more than using the REST API directly?
No — MCP and REST share the same credit pool and the same published endpoint rates. There's no separate MCP surcharge.

More integrations

Try it with 25 free credits.