---
updatedAt: 2026-10-15T17:21:32.000Z
agentTools:
  projectIndex: https://docs.delivr.ai/llms.txt
---

# Quickstart: Enrich Emails with Intent

## What this does

Send an email address (or a LinkedIn URL), get back the intent topics that person is actively researching, with scores. This is the fastest path to a working Delivr integration: one endpoint, one POST, useful data in the response.

```
email in  ->  POST /api/v1/intent/lookup  ->  topics + scores out
```

Typical uses: scoring leads in a CRM, an enrichment column in Clay or a spreadsheet workflow, prioritizing outreach by what a prospect is researching.

This endpoint answers from a **single day** of intent data, the day named by `as_of` in the response. It is not a rolling week. See [What `as_of` means](#what-as_of-means) below before comparing a lookup against anything built over a longer window, such as an Intent Audience.

## Before you start

You need an organization API key + secret pair. Create one in the dashboard under **Settings > API keys**; the [Authentication](https://docs.delivr.ai/docs/authentication) guide walks through it. Every request below sends both headers:

```
X-Api-Key: dlvr_...
X-Api-Secret: ...
```

## 1. Look up one person

```bash
curl -X POST "https://api.delivr.ai/api/v1/intent/lookup" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"id": "row-42", "email": "jane@acme.com"}'
```

### Response (200 OK)

```json
{
  "response": {
    "id": "row-42",
    "matched": true,
    "matched_on": "email",
    "person_id": "c41f6a0d92b7e5381a4d7f0e63b9a2c58d1e9f47b30a6c25e8d7149fa3b06e21",
    "topics": [
      {
        "topic_id": "4eyes_103456",
        "topic_name": "CRM Software",
        "score": "high",
        "perc_score": 87
      }
    ]
  },
  "as_of": "2026-06-19"
}
```

How to read it:

* `id` is your own opaque row identifier, echoed back. Use it to join results to your source data.
* `matched: false` with an empty `topics` array means the person is not in the current intent data. That is a normal outcome, never an error.
* `matched_on` names the identifier that was resolved (`person_id`, `hem`, `email` or `linkedin_url`).
* `person_id` is an id for the person (64 lowercase hex characters, opaque to you). A small share of ids change when our identity data is rebuilt, so keep the email or `hem` alongside it, and do not confuse it with the `profile_pid_all` column in audience exports. It is returned whenever the identifier resolved to a person. Send it back as `person_id` on a later lookup, or on a batch item, to read the same person directly. It takes precedence over any other identifier on the same item.
* Intent is matched to the person, not to one address. An `email`, `hem` or `linkedin_url` resolves to the person who holds it, and `topics` covers every address we hold for them, so an address with no intent of its own still matches when another of the person's addresses has intent. `resolved_across_addresses: true` marks a `hem` or `email` match where the person holds at least one address other than the one you sent; the other address does not need intent of its own. It is not set for `linkedin_url` or `person_id` matches.
* An opted-out identifier never matches.
* `score` is the intent tier (`high`, `medium` or `low`); `perc_score` is a 1-99 strength rating for that person on that topic, derived from their own recent activity on it. It is not a percentile and not a rank against other people, so repeated values are expected. A strong `perc_score` can sit next to a `medium` tier for people who research thousands of topics; see [Intent Audience Fields](https://docs.delivr.ai/docs/intent-audience-fields#score-details).
* `as_of` is the single calendar day the intent dataset covers. See below.
* You can send `linkedin_url` instead of `email`. A LinkedIn URL identifies the person, who is matched across every address we hold for them, the same as an email or hem.
* An address we cannot resolve to a person matches on its own intent and returns no `person_id`. For those addresses, `resolve_person: true` still widens the match to the person's other addresses.

### What `as_of` means

`as_of` names **one calendar day**, not a cut-off with history behind it. The intent dataset is rebuilt from that day's activity and replaced whole, every day. Three consequences worth knowing before you compare results:

* **A person can match one day and not the next**, without anything having changed about them or about your request. If someone researched a topic on the 26th, a lookup served from the 1st will not show it, and `matched: false` is the correct answer for that day.
* **All three tiers are returned by default.** Send `min_score: "medium"` or `"high"` to drop the weaker tiers.
* **Each person carries at most 1,000 topics** on a given day. For the small share of people researching more than that, the response is the strongest 1,000, not an exhaustive list.

If you need activity across a range of days rather than one, that is what an [Intent Audience](https://docs.delivr.ai/docs/delivr-audience) builds: it reads its configured range (a relative `intent_window_days`, or a fixed `intent_start_date`-`intent_end_date` window) and, depending on `row_grain`, keeps either each person's single strongest topic across it or every topic they matched. The two can legitimately disagree for the same person on the same day, because they measure different spans. See [Which API should I use?](https://docs.delivr.ai/docs/which-api-should-i-use).

## 2. Look up a list (up to 10,000 per call)

`POST /api/v1/intent/batch` takes up to **10,000 items** per request, mixing `email` and `linkedin_url` freely. Results come back in input order, one per item.

```bash
curl -X POST "https://api.delivr.ai/api/v1/intent/batch" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {"id": "r1", "email": "jane@acme.com"},
      {"id": "r2", "linkedin_url": "https://www.linkedin.com/in/jane-doe-87745243"},
      {"id": "r3", "email": "bob@nope.com"}
    ]
  }'
```

The response carries `matched_count` plus a `results` array shaped like the single lookup, including `person_id` on each item that resolved to a person. Unmatched items simply come back `matched: false`. A batch item can also carry a `person_id` instead of an email or LinkedIn URL.

## 3. Preview cost before you run (dry\_run)

Every intent endpoint accepts `"dry_run": true`. The API resolves your request, tells you exactly how many records a live run would bill, and **charges nothing**:

```bash
curl -X POST "https://api.delivr.ai/api/v1/intent/batch" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"items": [...], "dry_run": true}'
```

```json
{
  "dry_run": true,
  "meter": "intent_signal_scored",
  "records_to_charge": 2
}
```

You are billed per **matched** record only; unmatched lookups cost nothing. Because any address a person holds resolves them, more lookups can match than when each address was read on its own, so a `dry_run` count can be higher than before. The people-by-topic endpoints bill one row per person. Details in [Rate Limits and Metering](https://docs.delivr.ai/docs/rate-limits-and-metering).

## Complete example: enrich a CSV (Python)

```python
import csv
import requests

API = "https://api.delivr.ai/api/v1/intent/batch"
HEADERS = {
    "X-Api-Key": "YOUR_API_KEY",
    "X-Api-Secret": "YOUR_API_SECRET",
}
BATCH_SIZE = 10_000  # server-side maximum per call

def batches(rows, size):
    for i in range(0, len(rows), size):
        yield rows[i : i + size]

with open("leads.csv") as f:
    rows = [{"id": str(i), "email": r["email"]} for i, r in enumerate(csv.DictReader(f))]

enriched = {}
for chunk in batches(rows, BATCH_SIZE):
    resp = requests.post(API, headers=HEADERS, json={"items": chunk}, timeout=60)
    resp.raise_for_status()
    for result in resp.json()["response"]["results"]:
        if result["matched"]:
            top = result["topics"][0] if result["topics"] else None
            enriched[result["id"]] = {
                "top_topic": top["topic_name"] if top else "",
                "score": top["score"] if top else "",
                "topic_count": len(result["topics"]),
            }

print(f"matched {len(enriched)} of {len(rows)}")
```

## Complete example: enrich one row (JavaScript)

```javascript
const resp = await fetch('https://api.delivr.ai/api/v1/intent/lookup', {
  method: 'POST',
  headers: {
    'X-Api-Key': process.env.DELIVR_API_KEY,
    'X-Api-Secret': process.env.DELIVR_API_SECRET,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ id: 'lead-1', email: 'jane@acme.com' }),
});
if (!resp.ok) throw new Error(`Delivr API ${resp.status}`);

const { response } = await resp.json();
if (response.matched) {
  const researching = response.topics.map((t) => t.topic_name).join(', ');
  console.log(`Researching: ${researching}`);
}
```

## Errors you might see

| Status                                     | Meaning                                                                     | Fix                                                                                                                                                                                                       |
| ------------------------------------------ | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`                                      | Malformed request (missing `email`/`linkedin_url`, batch over 10,000 items) | Check the request body against the examples above                                                                                                                                                         |
| `401`                                      | Missing or invalid key/secret pair                                          | Re-copy both halves from the dashboard; see [Authentication](https://docs.delivr.ai/docs/authentication)                                                                                                  |
| `429` with `error` `platform_cap_exceeded` | Over a daily or monthly usage limit                                         | Do not retry straight away: `kind` says whether to wait for `resets_at`, send a smaller request, or contact support; see [Rate Limits and Metering](https://docs.delivr.ai/docs/rate-limits-and-metering) |
| Any other `429`                            | Over your request rate limit (default 10 requests/sec per organization)     | Back off and retry; see [Rate Limits and Metering](https://docs.delivr.ai/docs/rate-limits-and-metering)                                                                                                  |

An unmatched identifier is **not** an error: it returns `200` with `matched: false`.

## Where to go next

* **Company-level intent**: `POST /api/v1/intent/company` scores a company domain the same way. Same auth, same shape.
* **Discovery (topic to people/companies)**: given a topic, list the people or companies showing intent with `POST /api/v1/intent/people` and `/api/v1/intent/companies`. Free capped previews exist at `/preview` variants so you can see real records before importing.
* **[Which API should I use?](https://docs.delivr.ai/docs/which-api-should-i-use)** if your use case is not a per-row lookup.
* **[Full API reference](https://docs.delivr.ai/reference)** for every field on every endpoint.