Integrations · LangChain
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.
Last updated September 22, 2026
Create a RichAPI account at app.richapi.ai/signup and copy your API key.
25 free credits, no card.
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.
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.
Bind them with llm.bind_tools([...]) for routing, or call the same functions directly inside an LCEL chain when the sequence is fixed.
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.
Copy, paste, edit the brackets, run.
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.
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.
```python 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.
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: ```python 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.
`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.
| 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](/pricing). Your LangSmith traces can stop saying "tool returned null" and start naming the providers that had nothing. Surface `execution_log` in your tool's return value; a multi-provider waterfall response puts one entry in it per provider attempt, each with a status.
Lookups only. No sending, no sequencing, no CRM writes, no prospecting UI. We do not publish the provider roster either. It changes, and the `execution_log` is a better answer than a list on a page. Related, from the [integrations index](/integrations): [CrewAI](/integrations/crewai) for role-based crews, [LangGraph](/integrations/langgraph) when the loop needs explicit state, [OpenAI Agents SDK](/integrations/openai-agents), and [person enrichment API](/api/person-enrichment) for the raw field shapes.
**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 provider 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](/use-cases/per-client-api-keys-for-agencies).
25 free credits, no card. Copy the tool, run the chain over ten domains, and read the traces before you write the agent.