> Enrich.chat gives AI agents verified work emails and mobiles. To use it from an agent, read https://enrich.chat/agents.md . This is the Markdown version of https://enrich.chat/features/api/

# A contact and company data API: count first, then search, then enrich

Plain HTTPS and JSON, free counts, cursor paging, a batch of 25 and background jobs of up to 5,000 rows. Errors tell you the fix, and an OpenAPI spec describes every endpoint.

By Enrich.chat Editorial. Updated October 19, 2026.

**Short answer:** Enrich.chat's API is a plain HTTPS contact and company data API that answers JSON. Count a search for free with `/v1/search/count`, page through results with a cursor, then enrich people by id, one at a time, 25 per batch, or as a background job of up to 5,000 rows with a signed webhook when it ends. An [OpenAPI spec](https://enrich.chat/openapi.json) describes all of it, and 10 lookups a day need no key.

## Key takeaways

- Lookups and searches are GET requests that return JSON. POST starts batches, background jobs, match tiers and webhooks. No SDK is needed, and SDKs are planned.
- Counts (`/v1/search/count`, `/v1/companies/count`) and filter lists (`/v1/facets/industry`, `/v1/filters`) cost 0 credits. A search returns 25 people a page and up to 1,000 per search.
- `POST /v1/jobs` runs up to 5,000 rows on the server and keeps results 30 days. Signed webhooks announce `job.completed` and `job.stopped`.
- Errors carry `error`, `message`, `fix`, `example` and, for a wrong filter value, `did_you_mean`. The OpenAPI 3.1 spec is at `/openapi.json`.
- 10 lookups a day need no key. On a plan, a work email found uses 10 credits, a mobile 250 and a person in a search 5.

## A data API that is easy to build on

- **Plain HTTPS, plain JSON.** Lookups and searches are GET requests with query parameters. POST starts batches, jobs, match tiers and webhooks. No SDK is needed.
- **Free counts and filter lists.** `/v1/search/count`, `/v1/companies/count` and `/v1/facets` cost 0 credits. Size a list before you read it.
- **Cursor paging and opaque ids.** Send `next_cursor` back as `cursor`. Each person and company has an id such as `per_...` to pass back.
- **Errors that tell you the fix.** Every error has a stable code and a message, and many carry `fix`, `example` and `did_you_mean`.
- **Batches and background jobs.** `POST /v1/batch` runs 25 items in one call. `POST /v1/jobs` runs up to 5,000 on the server, and a signed webhook says when it ends.
- **OpenAPI 3.1 and agent docs.** The spec is at `/openapi.json`, the Swagger reference at `/docs/`, and the agent guide at `/agents.md`.

## Count, search, enrich

1. **Count.** Call `GET /v1/search/count` with a sentence or filters. It is free and returns `interpreted_as`.
2. **Search.** Call `GET /v1/search` for 25 people a page. Send `next_cursor` back as `cursor`, up to 40 pages.
3. **Enrich.** Pass each id as `person_id` to `/v1/person`, send up to 25 items to `POST /v1/batch`, or start a background job of up to 5,000.

## What does the Enrich.chat contact data API include?

A contact data API returns facts about people, such as a job title, a work email and a mobile number. A company data API returns facts about organizations, such as industry and size. [Cognism's guide](https://www.cognism.com/blog/contact-data-api) draws the same line: company data defines the market, and contact data names the people to reach inside it. Enrich.chat's API does both under one base URL, `https://enrich.chat/v1`.

Lookups and searches are GET requests with query parameters, and every answer is JSON. POST is for batches, match tiers, background jobs and webhooks. The table lists each endpoint and what it costs.

| Endpoint | Use it to | Credits |
| --- | --- | --- |
| `GET /v1/search/count` | Count people for a sentence or filters | 0 |
| `GET /v1/search` | Find people, 25 per page | 5 per person returned |
| `GET /v1/person` | Get one person's profile, work email and mobile | 10 email, 250 mobile, 5 profile |
| `GET /v1/email`, `/v1/mobile` | Get one work email or one mobile | 10 email, 250 mobile |
| `GET /v1/companies/count`, `/v1/companies` | Count companies, or search them | 0 for the count, 5 per company returned |
| `GET /v1/company`, `/v1/company/tech` | Get a company profile, or its tech stack | 5 profile, 5 tech stack |
| `GET /v1/company/people` | List people at one company website | 5 per person returned |
| `GET /v1/email-pattern` | Get the email formats a company uses | 5 |
| `GET /v1/filters`, `/v1/facets/industry` | List every filter, or the valid values of one | 0 |
| `POST /v1/search/cascade` | Search with up to five match tiers, strictest first | 5 per person returned, 0 for `count_only` |
| `POST /v1/batch` | Run up to 25 items in one call | Each item as its single call |
| `POST /v1/jobs`, `GET /v1/jobs/{id}` | Run up to 5,000 items on the server, then check the job and fetch results | Each row as its single call |
| `POST /v1/webhooks` | Be told when a job finishes or stops | 0 |
| `GET /v1/usage` | See free lookups left, or plan credits | 0 |

A lookup that finds nothing uses 0 credits. Each matched answer shows `credits_used`. For a single person by LinkedIn URL, email, or name and company website, see the [person enrichment API](https://enrich.chat/api/person-enrichment/).

## How do you go from a count to a list to a contact?

Start with the free count, read the list a page at a time, then get contact details for the people you keep. The first step costs nothing, and the later steps spend credits only on what comes back.

**Figure 1: The three-step call sequence: count, search, enrich.** Step one, GET /v1/search/count, takes the same sentence and filters as a search and is free. Step two, GET /v1/search, returns 25 people a page and uses 5 credits per person. Step three, GET /v1/person with a person_id, or a batch of 25, uses 10 credits for a work email and 250 credits for a mobile. Counts, filter lists and lookups that find nothing are always free.

*The usual call sequence and what each step costs. Source: [Enrich.chat OpenAPI spec](https://enrich.chat/openapi.json).*

**Count.** `GET /v1/search/count` takes the same `q` and filters as a search. It returns `count` and `interpreted_as`, so you see how many people match and how the request was read. `/v1/companies/count` does the same for companies. The [AI lead search](https://enrich.chat/features/ai-lead-search/) page explains how a sentence becomes filters.

**Search.** `GET /v1/search` takes a sentence or filters, from job title and skills to funding and technology, and `GET /v1/filters` lists them all. It returns up to 25 people per page, best first. The answer carries `total_matches` when it is quick to work out and a `next_cursor`. Send the cursor back as `cursor` for the next page. One search can be paged 40 times, which is 1,000 people, and a cursor works only with the search that made it.

**Enrich.** Every person and company has an opaque id, `per_...` or `org_...`. Pass it as `person_id` to `/v1/person` or as `company_id` to `/v1/company`. For a list, `POST /v1/batch` runs up to 25 items in one call. Each item has an `op`, such as `person`, `email` or `company_tech`, and is billed like its single call. The answer is HTTP 200 even when some items fail, so read each item's `status`. To rank people by fit, send up to five tiers to [`POST /v1/search/cascade`](https://enrich.chat/features/match-tiers/).

**Figure 2: Anatomy of a search response.** A search response with its fields explained. found is false when nothing matched, which uses 0 credits. count is the people on this page. Each people entry has an id to pass as person_id. total_matches is how many match in all. next_cursor is sent back as cursor for the next page. interpreted_as shows how the request was read. credits_used is 5 credits per person returned. free_lookups_remaining appears on the free tier.

*A search response, field by field. Values are illustrative. Source: [Enrich.chat OpenAPI spec](https://enrich.chat/openapi.json).*

```text
Use www.enrich.chat to count VPs of Operations at US logistics companies with 200 to 1000 employees, tell me how you read the request, then list the first 25 and get the work email and its status for the three best fits
```

## How do background jobs and webhooks work?

`POST /v1/jobs` runs up to 5,000 lookups of one kind on the server, and they carry on after you disconnect. Pick an `op`: `person`, `email`, `mobile`, `company`, `company_tech` or `email_pattern`. A job belongs to an account, so it needs an API key, an OAuth token or a dashboard login. The answer is 202 with a job id.

**Figure 3: A background job from request to webhook.** Four steps between your app and Enrich.chat. Your app posts up to 5,000 items to /v1/jobs and gets 202 and a job id. Enrich.chat runs the job on its server, and it carries on if you disconnect. When the job ends, Enrich.chat posts a signed job.completed or job.stopped event to your webhook. Your app verifies the signature, then gets the results with GET /v1/jobs/id/results.

*A background job, from the request to the signed webhook. The job id is returned in step 1. Source: [Enrich.chat OpenAPI spec](https://enrich.chat/openapi.json).*

- **Status and results.** `GET /v1/jobs/{id}` shows queued, running, done, stopped or cancelled, with counts and credits used. `GET /v1/jobs/{id}/results` returns JSON a page at a time, or `?format=csv` for one file. Rows and results are kept 30 days.
- **Billing.** Each row is billed like its single call, and a row that finds nothing is free. If credits or free lookups run out, the job stops with the status stopped.
- **Control.** `POST /v1/jobs/{id}/cancel` cancels a job, and `DELETE /v1/jobs/{id}` deletes it with its results.

A webhook saves you from polling. `POST /v1/webhooks` takes an https address on a public host name and shows a signing secret (`whsec_...`) once. Every delivery is a POST with `Enrich-Event`, `Enrich-Delivery` and `Enrich-Signature` headers. Recompute the HMAC-SHA256 of `t.body` with your secret, compare it to `v1` and reject an old `t`, as the Verify webhook tab shows. A failed delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. After ten failures in a row the webhook is switched off until `POST /v1/webhooks/{id}/enable`. The only events are `job.completed` and `job.stopped`.

## How do errors, keys and rate limits work?

Every error is JSON with a stable `error` code and a `message`. Many also carry `fix`, an `example` request that works and, for a wrong filter value, `did_you_mean`.

| Status and code | What to do |
| --- | --- |
| 400 `unknown_filter_value` | Use a value from `did_you_mean`, or list the values with `GET /v1/facets/industry`. Listing is free. |
| 400 `search_too_broad` | The request names nobody in particular. Add a role, an industry, a place or a company. |
| 400 `bad_cursor`, `result_limit` | The cursor belongs to another search, or the search was paged 40 times. Narrow the filters. |
| 400 `bad_id` | Use an id that came from a search result. |
| 401 `invalid_api_key` | The key is wrong or revoked. Check it at [enrich.chat/account/](https://enrich.chat/account/), or leave it out for the free tier. |
| 402 `free_limit_reached` | Stop. The body has `upgrade_url` and `resets_at`. |
| 402 `credits_exhausted`, `plan_inactive` | Credits are used up until the plan renews, or billing needs a look at [enrich.chat/account/](https://enrich.chat/account/). |
| 429 `too_many_requests` | Wait `retry_after` seconds, then continue. |
| 503 `temporarily_unavailable` | Retry once after `retry_after`. Nothing was billed. |

An API key is optional for lookups. The free tier needs none: each IP address gets 10 lookups a day. To get a key, log in at [enrich.chat/account/](https://enrich.chat/account/) with your email, pick a plan, pay at checkout, and the key appears on that page. It starts with `pp_`. Send it as `Authorization: Bearer pp_...`. A `key` query parameter also works for tools that cannot set headers, but [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750), the standard for bearer tokens, advises against putting a token in a URL, so prefer the header.

Keep to about one request a second, and see the [API reference](https://enrich.chat/docs/) for rate limits. Past a limit the API answers 429 with `retry_after` and a `Retry-After` header. [RFC 6585](https://www.rfc-editor.org/rfc/rfc6585) defines the 429 status and says the response may include a Retry-After header.

## How does it compare with Apollo's and People Data Labs' search APIs?

All three let you search for people by filters. They differ in what you write, what a search costs and how far you can page. Checked on each vendor's documentation on October 19, 2026.

| API | Search input | What a search costs | Paging |
| --- | --- | --- | --- |
| Enrich.chat | Plain-English `q` or filters | Counts: 0 credits. People returned: 5 credits each, or one free lookup per search on the free tier | 25 a page with a cursor, up to 1,000 per search |
| Apollo | [Structured filters on `POST /mixed_people/api_search`](https://docs.apollo.io/reference/people-api-search) | [0 credits, and no emails or phone numbers in the results](https://docs.apollo.io/reference/people-api-search) | [100 a page, up to 500 pages (50,000 records)](https://docs.apollo.io/reference/people-api-search) |
| People Data Labs | [An Elasticsearch or SQL query](https://docs.peopledatalabs.com/docs/reference-person-search-api) | [1 credit per record returned](https://docs.peopledatalabs.com/docs/reference-person-search-api) | [1 to 100 a request, then a `scroll_token`](https://docs.peopledatalabs.com/docs/reference-person-search-api) |

The trade-offs are practical. Apollo's search costs no credits and reaches further, but its results leave out email addresses and phone numbers. Enrich.chat's results show whether an email and a mobile are on file, and you reveal them one person at a time. People Data Labs gives exact control, but you write the query yourself.

### When is another API the better choice?

- **Apollo** fits if you want to browse far past 1,000 results without spending credits on the search itself, per its documented 50,000-record limit.
- **People Data Labs** fits if your team already writes SQL or Elasticsearch and wants to page through large result sets with a scroll token.
- **Enrich.chat's Enterprise plan** fits if you need bulk files or a data license. It is a custom plan from $2,000 a month: ask sales@enrich.chat.

For a wider comparison, see [B2B contact data APIs compared](https://enrich.chat/blog/b2b-data-apis-compared/).

## How do agents and code get started?

Call the endpoints directly, import the spec, or hand the job to an agent. SDKs are planned, and plain HTTPS works today.

- **OpenAPI.** [enrich.chat/openapi.json](https://enrich.chat/openapi.json) is an OpenAPI 3.1 file. [The OpenAPI Specification](https://spec.openapis.org/oas/v3.1.0.html) is a language-independent way to describe HTTP APIs, so people and software can see what a service does without reading its source. Import it into any tool that reads OpenAPI. The [interactive Swagger reference](https://enrich.chat/docs/) reads the same file.
- **Agent docs.** [agents.md](https://enrich.chat/agents.md) covers the whole API on one page, and [SKILL.md](https://enrich.chat/SKILL.md) is a skill file. A prompt that starts "Use www.enrich.chat" is enough.
- **MCP.** Add `https://enrich.chat/mcp` with no sign-in and 10 free lookups a day, or `https://enrich.chat/mcp/oauth` to use your plan. Per the [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), sign-in builds on OAuth 2.1, clients must use PKCE, and dynamic client registration is recommended. See the [MCP server](https://enrich.chat/features/mcp-server/) page and the [install page](https://enrich.chat/install/).

## How much does the API cost, and where does it stop?

The free tier gives 10 lookups a day per IP address, with no signup and no key. Plans bill monthly and you can cancel anytime: Growth $29 for 100,000 credits, Scale $90 for 500,000 credits, Pro $299 for 2,500,000 credits. Every plan includes every product and unlimited seats. Credits renew with each monthly payment, and unused credits do not roll over. A team shares one credit pool, as [team workspaces](https://enrich.chat/features/team-workspaces/) explains. The [pricing page](https://enrich.chat/pricing/) has a calculator.

Here is a worked example. You read 1,000 people, reveal up to 200 work emails and up to 20 mobiles. Reading uses 5,000 credits, the emails up to 2,000 and the mobiles up to 5,000. That is up to 12,000 credits in the month, which fits the Growth plan at $29 a month. `GET /v1/usage` shows your free lookups left, or your plan's credits.

### What does the API not do?

- One search returns at most 1,000 results. Narrow the filters to go deeper.
- Work emails go in on the email input only. Personal addresses such as Gmail are not looked up, and company lookups take a website, not a company name.
- `employees_on_linkedin` is a signal for company size, not an exact headcount, and revenue is a band.
- Webhooks announce finished jobs only, not new matches or changes to people. A job holds at most 5,000 rows and keeps its results 30 days. The API does not send email, run sequences or track changes.
- There is no CRM sync, and a team has no per-person credit limits.

Anyone can opt out at [enrich.chat/opt-out/](https://enrich.chat/opt-out/), and opt-outs are applied to every result. You are responsible for the laws that apply to your outreach, such as CAN-SPAM, GDPR and TCPA. See the [terms of service](https://enrich.chat/terms/). For a CSV of rows, the dashboard has [bulk enrichment](https://enrich.chat/features/bulk-enrichment/), which can run as a job.

## Frequently asked questions

### What is a contact data API?

A contact data API returns facts about people, such as a job title, a work email and a mobile number, in answer to a request your code or agent sends. Enrich.chat's is a set of endpoints under `https://enrich.chat/v1` that return JSON. You can search people and companies, then enrich a person by id. A company data API returns facts about organizations, such as industry and size, and Enrich.chat offers those too.

### Is there a free contact data API, and do I need a key?

Yes. Each IP address gets 10 free lookups a day with no signup, no key and no Authorization header. Counts, filter lists and lookups that find nothing do not use a lookup. A key comes with a paid plan: log in at [enrich.chat/account/](https://enrich.chat/account/), pick a plan, and the key (it starts with `pp_`) appears after checkout. Plans start at $29 for 100,000 credits. A wrong key returns 401 `invalid_api_key`.

### How do I page through search results?

A search answer includes `next_cursor` when there is another page. Send it back as `cursor` with the same search to get the next 25 people. A cursor from a different search returns `bad_cursor`, and a search that has been paged 40 times, which is 1,000 people, returns `result_limit`. Narrow the filters to go deeper.

### Does Enrich.chat have an OpenAPI spec?

Yes. The spec is an OpenAPI 3.1 file at [enrich.chat/openapi.json](https://enrich.chat/openapi.json), and the interactive Swagger reference at [enrich.chat/docs/](https://enrich.chat/docs/) reads it. It lists every endpoint, parameter, response shape and error. The same API is written up for agents in [agents.md](https://enrich.chat/agents.md). SDKs are planned, but you do not need one: every endpoint is a plain GET request.

### Does the Enrich.chat API have background jobs and webhooks?

Yes. `POST /v1/jobs` runs up to 5,000 lookups of one kind on the server, and they carry on after you disconnect. Check `GET /v1/jobs/{id}` and fetch results as JSON or CSV within 30 days. `POST /v1/webhooks` registers an https address that gets a signed `job.completed` or `job.stopped` event. A job needs an API key, an OAuth token or a login. See [background jobs](https://enrich.chat/features/background-jobs/).

### How many lookups can I send in one call?

`POST /v1/batch` takes up to 25 items per call. Each item has an `op`, such as `person`, `email`, `mobile`, `company`, `company_tech` or `email_pattern`, and an id of your own. Each is billed like its single call, and one that finds nothing uses 0 credits. For more, `POST /v1/jobs` takes up to 5,000 items of one kind and runs them on the server. The dashboard's [bulk enrichment](https://enrich.chat/features/bulk-enrichment/) uses both.

## Sources

1. [Contact data API: what it is and how to choose one](https://www.cognism.com/blog/contact-data-api)
2. [People API Search](https://docs.apollo.io/reference/people-api-search)
3. [Person Search API reference](https://docs.peopledatalabs.com/docs/reference-person-search-api)
4. [OpenAPI Specification v3.1.0](https://spec.openapis.org/oas/v3.1.0.html)
5. [RFC 6585: Additional HTTP Status Codes](https://www.rfc-editor.org/rfc/rfc6585)
6. [RFC 6750: Bearer Token Usage](https://www.rfc-editor.org/rfc/rfc6750)
7. [Authorization (Model Context Protocol, 2025-06-18)](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)
8. [Enrich.chat OpenAPI specification](https://enrich.chat/openapi.json)
9. [Enrich.chat agent docs](https://enrich.chat/agents.md)

## Examples

**cURL**

```bash
# 1. Free count, and how the sentence was read
curl -G "https://enrich.chat/v1/search/count" \
  --data-urlencode "q=VPs of Operations at US logistics companies with 200 to 1000 employees"

# 2. 25 people, best first (add cursor=... for the next page)
curl -G "https://enrich.chat/v1/search" \
  --data-urlencode "q=VPs of Operations at US logistics companies with 200 to 1000 employees" \
  --data-urlencode "limit=25"

# 3. Work email and status for one result
curl "https://enrich.chat/v1/person?person_id=per_4Hk2x9QmVb7LrTzE&fields=email"

# The key is optional. With a paid key, add to any call:
#   -H "Authorization: Bearer $ENRICH_KEY"
```

**Python**

```python
import os
import time
import requests

BASE = "https://enrich.chat/v1"
Q = "VPs of Operations at US logistics companies with 200 to 1000 employees"

# Optional: a paid key from enrich.chat/account/ lifts the free limit
key = os.environ.get("ENRICH_KEY")
headers = {"Authorization": "Bearer " + key} if key else {}


def call(path, **params):
    time.sleep(1)  # about one request a second
    r = requests.get(BASE + path, params=params, headers=headers, timeout=30)
    body = r.json()
    if r.status_code == 429:
        time.sleep(body["retry_after"])
        return call(path, **params)
    if not r.ok:
        # 400 errors carry "fix" and "example"; 402 carries "upgrade_url"
        raise SystemExit(body["error"] + ": " + body["message"])
    return body


# 1. Free count, and how the sentence was read
counted = call("/search/count", q=Q)
print(counted["count"], counted["interpreted_as"])

# 2. First page of people, best first
page = call("/search", q=Q, limit=25)
if not page["found"]:
    raise SystemExit(page.get("hint", "no people, 0 credits"))
for p in page["people"]:
    print(p["id"], p["name"], p["title"], p["company"])
# Next page: call("/search", q=Q, limit=25, cursor=page["next_cursor"])

# 3. Work email and status for one person
person = call("/person", person_id=page["people"][0]["id"], fields="email")
print(person.get("work_email"), person.get("credits_used"))
```

**Search response**

```json
{
  "found": true,
  "count": 2,
  "people": [
    {"id": "per_4Hk2x9QmVb7LrTzE", "name": "Jane Doe",
     "title": "VP Operations", "company": "Acme Logistics",
     "company_domain": "acme.example.com", "level": "vp",
     "has_email": true, "has_mobile": true,
     "linkedin_url": "https://www.linkedin.com/in/jane-doe-ops"},
    {"id": "per_9Zt3cWn8KpD2yUaB", "name": "John Roe",
     "title": "VP Logistics", "company": "Hale Freight",
     "company_domain": "hale.example.com", "level": "vp",
     "has_email": true, "has_mobile": false,
     "linkedin_url": "https://www.linkedin.com/in/john-roe-log"}
  ],
  "total_matches": 18420,
  "next_cursor": "eyJyIjoyMn0",
  "interpreted_as": {"level": ["vp"], "country": ["United States"], ...},
  "credits_used": 10,
  "free_lookups_remaining": 9
}

# Illustrative people and values.
```

**Batch**

```bash
curl -X POST "https://enrich.chat/v1/batch" \
  -H "content-type: application/json" \
  -d '{"items": [
        {"id": "a", "op": "email", "name": "Jane Doe", "domain": "acme.example.com"},
        {"id": "b", "op": "email_pattern", "domain": "acme.example.com"}
      ]}'

{
  "count": 2,
  "credits_used": 15,
  "free_lookups_remaining": 8,
  "results": [
    {"id": "a", "status": 200,
     "result": {"found": true,
                "work_email": {"value": "jane.doe@acme.example.com", "status": "verified"},
                "credits_used": 10}},
    {"id": "b", "status": 200,
     "result": {"found": true, "domain": "acme.example.com",
                "patterns": [{"pattern": "first.last@acme.example.com", ...}],
                "credits_used": 5}}
  ]
}

# Up to 25 items per call. Illustrative people and values.
```

**Job**

```text
# Needs an account: an API key, an OAuth token or a login.
curl -X POST "https://enrich.chat/v1/jobs" \
  -H "Authorization: Bearer $ENRICH_KEY" \
  -H "content-type: application/json" \
  -d '{"op": "email", "label": "Q4 list", "items": [
        {"id": "a", "name": "Jane Doe", "domain": "acme.example.com"}
      ]}'

# 202 Accepted. Illustrative id.
{"id": "job_2xYb9QwErTy8UiOp", "status": "queued", "total": 1,
 "status_url": "https://enrich.chat/v1/jobs/job_2xYb9QwErTy8UiOp"}

# Then, until status is done or stopped:
#   GET /v1/jobs/{id}
# and when it ends:
#   GET /v1/jobs/{id}/results?format=csv

# Or skip polling. Register a webhook once (https only):
curl -X POST "https://enrich.chat/v1/webhooks" \
  -H "Authorization: Bearer $ENRICH_KEY" \
  -H "content-type: application/json" \
  -d '{"url": "https://example.com/hooks/enrich",
       "events": ["job.completed", "job.stopped"]}'
# The reply shows a whsec_... secret once. Keep it.
```

**Verify webhook**

```text
import hashlib
import hmac
import time


def verify(secret, header, raw_body):
    # header: Enrich-Signature  t=<unix seconds>,v1=<hex>
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False  # reject an old timestamp
    signed = parts["t"].encode() + b"." + raw_body
    good = hmac.new(secret.encode(), signed,
                    hashlib.sha256).hexdigest()
    return hmac.compare_digest(good, parts["v1"])

# Sign "<t>.<body>" with HMAC-SHA256 and the whsec_ secret.
# Answer 2xx once the event is stored.
```

## Prices

| What you get | Credits |
| --- | --- |
| Work email found | 10 |
| Mobile number found | 250 |
| Company tech stack or profile found | 5 |

10 free lookups a day for every IP address, no signup. Nothing is charged when nothing is found. Monthly plans start at $29 for 100,000 credits.

## Try this prompt

Count, search and enrich from your agent

```text
Use www.enrich.chat to count VPs of Operations at US logistics companies with 200 to 1000 employees, tell me how you read the request, then list the first 25 and get the work email and its status for the three best fits
```

## Related

- [AI lead search](https://enrich.chat/features/ai-lead-search/)
- [MCP server with OAuth](https://enrich.chat/features/mcp-server/)
- [Bulk enrichment from a CSV](https://enrich.chat/features/bulk-enrichment/)
