Integrations · LangChain

LangChain Enrichment — Typed Tools and Chains over RichAPI

Define RichAPI endpoints as @tool functions with Pydantic argument schemas so the model fills typed fields instead of guessing JSON. Most enrichment does not need an agent at all, and a chain is cheaper and deterministic. Use tool-calling only where the routing is genuinely ambiguous.

In short: RichAPI works with LangChain either as hand-written tools using the @tool decorator from langchain_core.tools with a Pydantic args_schema, or by loading RichAPI's hosted Model Context Protocol server at https://mcp.richapi.ai/mcp through langchain-mcp-adapters. There is no RichAPI package in the langchain-community integrations registry. Tools call the REST API at https://api.richapi.ai/api/v1/{endpoint} with an x-api-key header. Endpoints include enrich_company and enrich_profile (both accept a LinkedIn URL, not a domain), email_finder, email_verifier, phone_finder, people_search, find_linkedin_url_by_email, find_linkedin_url_by_name and web_tech_stack. Waterfall endpoints (email_finder, email_verifier, phone_finder) return HTTP 200 with success false and billed false when nothing is found and cost zero credits; all other endpoints bill on a successful 2xx response, including an empty one. `execution_log` arrives on waterfall calls as one entry per attempt with a status. Log it from the chain and a run that returns nothing stops being a mystery. Fixed enrichment sequences are better expressed as LCEL chains than as agents, since a chain makes exactly the calls it is written to make. RichAPI performs lookups only; it does not send email or write to a CRM. New accounts get 100 free credits with no card.

Last updated November 4, 2026

Connect it

  1. 1. Create a RichAPI account at app.richapi.ai/signup and copy your API key.

    100 free credits, no card.

  2. 2. pip install langchain langchain-core, and export RICHAPI_KEY.

    Add langchain-mcp-adapters instead if you would rather load the whole catalog from our MCP server than hand-write wrappers.

  3. 3. Write one @tool per endpoint with a Pydantic args_schema.

    The field descriptions are the prompt. company_domain described as 'bare domain, no scheme or www' is the difference between acme.com and https://www.acme.com/ and a wasted call.

  4. 4. Bind them with llm.bind_tools([...]) for routing, or call the same functions directly inside an LCEL chain when the sequence is fixed.

  5. 5. Parse the response with a Pydantic output model so a miss is a typed None rather than a string the next step has to interpret.

LangChain

What you can ask for

Chain: domain in → enrich_company
Chain: domain in → enrich_company → structured ICP score out, as a Pydantic model with a reason field.
Router: given a partial contact,
Router: given a partial contact, decide between email_finder, find_linkedin_url_by_name and find_linkedin_url_by_email, then run it.
RAG over your own account
RAG over your own account notes, with a live enrich_company call to refresh headcount and industry before the model answers.
Batch 500 rows through a
Batch 500 rows through a RunnableLambda calling email_verifier, split into valid, catch-all and invalid.
Tool-calling agent that researches an
Tool-calling agent that researches an account and refuses to answer a field it could not retrieve.

Why it Matters

LangChain's real contribution to enrichment is not the agent loop. It is typed arguments and typed outputs. A model that has to satisfy a Pydantic schema sends fewer malformed requests, and every malformed request to a per-call endpoint is a credit you paid for nothing.

LangChain enrichment: typed arguments are what save you credits

There is no RichAPI package in langchain-community. You define the tools, and LangChain gives you the one thing that matters for a paid API: the model fills a schema you wrote rather than assembling JSON from vibes.

A tool with a schema that actually constrains

import os, requests
from typing import Optional
from pydantic import BaseModel, Field
from langchain_core.tools import tool

class EmailResult(BaseModel):
    email: Optional[str] = None
    status: Optional[str] = None
    found: bool
    providers_tried: list[str] = []

@tool(args_schema=...)
def find_work_email(full_name: str, company_domain: str) -> EmailResult:
    """Find a verified work email. company_domain must be a bare domain
    such as acme.com. No scheme, no www, no path."""
    r = requests.post(
        "https://api.richapi.ai/api/v1/email_finder",
        headers={"x-api-key": os.environ["RICHAPI_KEY"]},
        json={"full_name": full_name, "company_domain": company_domain},
    ).json()
    if not r.get("success"):
        return EmailResult(found=False,
            providers_tried=[a.get("status") for a in r.get("execution_log", [])])
    return EmailResult(found=True, email=r["data"]["email"], status=r["data"]["status"])

The docstring is the tool description the model reads, and the sentence about bare domains is load-bearing. Models hand APIs https://www.acme.com/about constantly. On email_finder a malformed domain costs nothing, because the waterfall soft-fails free. On enrich_profile, which bills on any 2xx, it costs you a credit to learn that the model added a trailing slash.

Returning a typed EmailResult rather than a dict means the downstream step branches on found, and a miss cannot silently become the string "None" in a merge field.

Do not reach for an agent first

A lot of LangChain enrichment code is a tool-calling agent doing work that has no decisions in it. If your pipeline is domain → company → decision-maker → email → verify, that is a chain. Write it as one:

pipeline = (
    RunnableLambda(clean_domain)
    | RunnableLambda(enrich_company)
    | RunnableLambda(top_contact)
    | RunnableLambda(find_work_email)
)
rows = pipeline.batch(domains, config={"max_concurrency": 8})

A chain makes exactly the calls it is written to make. An agent makes the calls it decides to make, and you find out how many on the invoice. Reach for bind_tools when the routing is genuinely ambiguous. You have a name and maybe a domain and maybe a LinkedIn URL, and something has to choose between email_finder, find_linkedin_url_by_name and find_linkedin_url_by_email. That is a real decision. "Call these four things in order" is not.

The structured-output side is the same argument pointed the other way. Wrap the enriched company in llm.with_structured_output(ICPScore) and you get a typed score with a reason field, from live data, without an agent loop anywhere.

MCP, if you would rather not maintain wrappers

langchain-mcp-adapters loads a remote MCP server's tools as LangChain tools. Ours is at https://mcp.richapi.ai/mcp with the same x-api-key header and the same credit pool, so the whole catalog arrives typed and you maintain none of it.

The tradeoff is the tool descriptions become ours rather than yours, and the domain-format sentence above is exactly the kind of thing you want to own. Wrappers for a narrow production pipeline, MCP for exploration.

Cost

Endpoint Cost Miss behaviour
email_finder 5 credits free
email_verifier 2 credits free
phone_finder 25 credits free
enrich_company 1 credit billed on 2xx
enrich_profile 1 credit billed on 2xx
find_linkedin_url_by_name 4 credits billed on 2xx
people_search 0.1 credits per result billed on 2xx

Full price only on results is true of the waterfall column and nothing else. A non-2xx never bills on any endpoint. Tiers on /pricing.

Your LangSmith traces can stop saying "tool returned null" and start naming the sources that had nothing. Surface execution_log in your tool's return value; a waterfall response puts one entry in it per attempt, each with a status.

Where we stop with LangChain

Lookups only. No sending, no sequencing, no CRM writes, no prospecting UI. We do not publish the list of sources either. It changes, and the execution_log is a better answer than a list on a page.

Related, from the integrations index: CrewAI for role-based crews, LangGraph when the loop needs explicit state, OpenAI Agents SDK, and person enrichment API for the raw field shapes.

LangChain FAQ

Is there a langchain-community RichAPI integration? No. @tool wrappers or langchain-mcp-adapters.

Does enrich_company take a domain? It takes a LinkedIn company URL. If you have a name, resolve it first. This trips up more integrations than anything else in the catalog.

Async? Define the tool with async def and use ainvoke / abatch. It is a plain HTTP POST underneath.

How do I stop an agent looping on a miss? Return found=False with the source statuses rather than raising. A tool that errors invites a retry; a tool that reports an exhausted waterfall does not.

Can I use the same key from a notebook and production? Yes, one pool. Name separate keys per surface if you want usage split out. See per-client API keys for agencies.

Start with LangChain

100 free credits, no card. Copy the tool, run the chain over ten domains, and read the traces before you write the agent.

FAQ

Frequently asked questions

No — LangChain Enrichment — Typed Tools and Chains over RichAPI 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.
No — MCP and REST share the same credit pool and the same published endpoint rates. There's no separate MCP surcharge.

More from RichAPI

More companies

Browse company profiles with company overviews, key details, and email formats.

Start free with 100 credits

Get 100 free credits to explore RichAPI with no card required.

Free tools

Use our Email Permutator, llms.txt Generator, Waterfall Calculator, and live API demo — all free to try.

Related integrations