> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fuseai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Data API

> Count people and companies, look up a company, find a person's email or phone, verify an address, or enrich up to 100 people in one job, at the prices of your key's usage plan.

The endpoints under `/business/data` each answer one question and save nothing to your Fuse workspace. They are built for products that call Fuse for their own users, so every call is priced and limited by the usage plan on the API key that makes it.

| Endpoint | Plan entry (`api`) | What it answers | Charged |
| - | - | - | - |
| [`GET /business/data/pricing`](https://docs.fuseai.com/api-reference/data/get-your-keys-usage-plan) | `pricing` | Your key's usage plan | Free |
| [`POST /business/data/people/count`](https://docs.fuseai.com/api-reference/data/count-people-matching-a-search-free) | `people_count` | How many people match a search | Free |
| [`POST /business/data/companies/count`](https://docs.fuseai.com/api-reference/data/count-companies-matching-a-search-free) | `company_count` | How many companies match a search | Free |
| [`POST /business/data/companies/resolve`](https://docs.fuseai.com/api-reference/data/find-a-companys-domain-linkedin-page-and-logo-free) | `company_resolve` | A company's domain, LinkedIn page and logo | Free |
| [`POST /business/data/people/email`](https://docs.fuseai.com/api-reference/data/find-a-persons-email) | `people_email` | A person's email | When one is found |
| [`POST /business/data/people/phone`](https://docs.fuseai.com/api-reference/data/find-a-persons-phone) | `people_phone` | A person's phone | When one is found |
| [`POST /business/data/emails/verify`](https://docs.fuseai.com/api-reference/data/verify-an-email-address) | `email_verify` | Whether an address can be sent to | Every verdict but `unknown` |
| [`POST /business/data/people/enrich-jobs`](https://docs.fuseai.com/api-reference/data/find-emails-and-phones-for-up-to-100-people) | `people_enrich_jobs` | Emails and phones for up to 100 people | Each value found, at the `people_email` and `people_phone` prices |

> [!NOTE]
>
> Every endpoint on this page needs the `data` [scope](https://docs.fuseai.com/concepts/scopes.md) and a key with a usage plan. A key without one can call `GET /business/data/pricing` only: every other data endpoint answers `403 API_NOT_ON_PLAN`. Fuse sets usage plans up per partner, so ask your Fuse contact for one.

## Usage plans

A usage plan belongs to one API key. For each data endpoint the key may call, it sets what one charged call costs in credits, and how many calls the key may make a minute and a day. Read your key's plan:

```bash theme={null}
curl https://api.tryfuse.ai/api/v1/business/data/pricing \
  -u "af_YOUR_KEY:"
```

```json theme={null}
{
  "plan": {
    "name": "Partner",
    "apis": [
      { "api": "pricing", "credits": 0, "perMinute": 60, "perDay": 10000 },
      { "api": "people_count", "credits": 0, "perMinute": 60, "perDay": 5000 },
      { "api": "people_email", "credits": 20, "perMinute": 60, "perDay": 5000 },
      { "api": "people_phone", "credits": 200, "perMinute": 60, "perDay": 5000 },
      { "api": "email_verify", "credits": 10, "perMinute": 60, "perDay": 5000 },
      { "api": "people_enrich_jobs", "credits": 0, "perMinute": 5, "perDay": 200 }
    ]
  }
}
```

* An endpoint missing from `apis` answers `403 API_NOT_ON_PLAN`.
* `credits` come out of your workspace's [balance](https://docs.fuseai.com/concepts/credits.md), the same one the app spends. Counts and company lookups are free whatever the plan lists.
* `perMinute` and `perDay` are counted for this key alone: your other keys and your teammates' keys do not share them. A call past either answers `429 RATE_LIMITED` with a `Retry-After` header, see [Rate limits](https://docs.fuseai.com/concepts/rate-limits.md).

## Count people or companies

A count is free and lists nobody, so use it to size or tune a search before you pay for rows. Send `POST /business/data/people/count` the filter source `POST /business/prospects/search` takes, exactly one of a plain-English `query`, `filters`, or a saved search by `savedSearchId` or `savedSearchName`. `POST /business/data/companies/count` takes the filter source of `POST /business/companies/search` the same way. Neither takes paging fields.

```bash theme={null}
curl https://api.tryfuse.ai/api/v1/business/data/people/count \
  -u "af_YOUR_KEY:" \
  -H "Content-Type: application/json" \
  -d '{ "query": "VPs of sales in the United States" }'
```

A new count runs in the background. The first answer is `202` with `status: "pending"` and the `filters` being counted:

```json theme={null}
{
  "status": "pending",
  "total": null,
  "filters": {
    "job_title_levels": [{ "status": "include", "value": "vp" }],
    "job_title_role": [{ "status": "include", "value": "sales" }],
    "location_country": [{ "status": "include", "value": "united states" }]
  },
  "unresolvedCriteria": [],
  "closestCompanyMatches": [],
  "creditsCharged": 0
}
```

Send the request again every few seconds with those `filters` in place of the `query`, until it answers `200` with `status: "ready"` and the `total`:

```bash theme={null}
curl https://api.tryfuse.ai/api/v1/business/data/people/count \
  -u "af_YOUR_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "job_title_levels": [{ "status": "include", "value": "vp" }],
      "job_title_role": [{ "status": "include", "value": "sales" }],
      "location_country": [{ "status": "include", "value": "united states" }]
    }
  }'
```

```json theme={null}
{
  "status": "ready",
  "total": 18432,
  "filters": {
    "job_title_levels": [{ "status": "include", "value": "vp" }],
    "job_title_role": [{ "status": "include", "value": "sales" }],
    "location_country": [{ "status": "include", "value": "united states" }]
  },
  "creditsCharged": 0
}
```

* Repeat with the returned `filters`, never the `query`: a `query` is converted again on every call, and the new conversion can count a different search.
* `unresolvedCriteria` (the parts of a `query` no filter could express) and `closestCompanyMatches` (each company the `query` named that had to be chosen among same-named companies) come back only on a `query` call, so keep them from the first answer.
* A people count can take up to about a minute and a half, a company count usually a few seconds. A search counted in the last 15 minutes answers `200` at once.
* `filters` is what was counted. Send it to `POST /business/prospects/search` or `POST /business/companies/search` to list the matches.
* A count that failed answers `503`: retry in a minute. Each account starts at most 10 new people counts a minute and 500 a day, and past that a new people count answers `503` too.

## Look up a company

`POST /business/data/companies/resolve` finds a company's website domain, LinkedIn page and logo, for free. Send at least one of `linkedinUrl`, `domain` or `name`. When you send several, only the most precise is looked up, in that order. A name resolves to the best-known company of that name, so send a domain or a LinkedIn page for common names.

```bash theme={null}
curl https://api.tryfuse.ai/api/v1/business/data/companies/resolve \
  -u "af_YOUR_KEY:" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "stripe.com" }'
```

```json theme={null}
{
  "status": "found",
  "company": {
    "name": "Stripe",
    "domain": "stripe.com",
    "linkedinUrl": "linkedin.com/company/stripe",
    "logoUrl": "https://logo.example.com/stripe.png"
  },
  "creditsCharged": 0
}
```

A company nothing matches answers `200` with `status: "not_found"` and `company: null`, never a guess. A field of `company` is null when it is not known. When the company data provider cannot answer in time the answer is `504`, and when it is busy, `429` without a `Retry-After` header: retry shortly in both cases. To get the full company record, pass the `domain` to `POST /business/companies/search` as `filters.domains`.

## Find a person's email or phone

`POST /business/data/people/email` and `POST /business/data/people/phone` look one person up. Name them by `linkedinUrl`, the most precise, or by `name` with a `companyName` or `companyDomain`.

```bash theme={null}
curl https://api.tryfuse.ai/api/v1/business/data/people/email \
  -u "af_YOUR_KEY:" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: 5d1c7e0a-jane-doe-email" \
  -d '{ "linkedinUrl": "https://www.linkedin.com/in/jane-doe" }'
```

```json theme={null}
{
  "status": "found",
  "email": { "value": "jane@stripe.com", "status": "valid", "type": "work" },
  "linkedinUrl": "linkedin.com/in/jane-doe",
  "creditsCharged": 20
}
```

* The plan's `people_email` or `people_phone` price is charged only when a value is found. `status: "not_found"`, with the value null, costs nothing.
* `email.status` is `valid`, `risky` (the domain accepts every address, so this one cannot be confirmed) or `unknown`, and `email.type` is `work` or `personal`. A phone comes back as `{ "value": "+14155550123" }`.
* `linkedinUrl` is the profile the lookup matched, or null.
* A value Fuse already holds answers at once. Otherwise Fuse asks its data providers in turn, which takes about 10 to 25 seconds. When nothing is found in time the answer is `504` and nothing is charged: retry.
* Before anything is looked up, the balance must cover the price, or the answer is `402 INSUFFICIENT_CREDITS`. A person who cannot be looked up (no `linkedinUrl`, and no `name` with a company that resolves) answers `422 PERSON_NOT_IDENTIFIABLE`, free.

## Verify an email

`POST /business/data/emails/verify` checks whether an address can be sent to.

```bash theme={null}
curl https://api.tryfuse.ai/api/v1/business/data/emails/verify \
  -u "af_YOUR_KEY:" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: 9a4f2b61-verify-jane" \
  -d '{ "email": "jane@stripe.com" }'
```

```json theme={null}
{ "email": "jane@stripe.com", "status": "valid", "creditsCharged": 10 }
```

`status` is `valid`, `invalid` (this includes addresses never to be sent to), `risky` (a catch-all domain that accepts every address) or `unknown`. Every verdict but `unknown` costs the plan's `email_verify` price. A check that cannot finish in time answers `504`, free: retry.

## Enrich up to 100 people in one job

For more than a handful of people, start a job instead of calling the lookups one by one. It runs in the background, and each row is charged like a single lookup, only for the values found.

```bash theme={null}
curl https://api.tryfuse.ai/api/v1/business/data/people/enrich-jobs \
  -u "af_YOUR_KEY:" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: 2c9e4b7a-batch-0001" \
  -d '{
    "people": [
      { "linkedinUrl": "https://www.linkedin.com/in/jane-doe" },
      { "name": "John Smith", "companyDomain": "example.com" }
    ],
    "find": ["email", "phone"]
  }'
```

```json theme={null}
{ "jobId": "6a1f2c3d4e5f6a7b8c9d0e1f", "status": "pending" }
```

The plan must include `people_enrich_jobs`, and `people_email` or `people_phone` for each kind `find` asks for. Before the job starts, the balance must cover a value of every kind asked on every row, or the answer is `402 INSUFFICIENT_CREDITS` and nothing runs.

Poll [`GET /business/data/people/enrich-jobs/{jobId}`](https://docs.fuseai.com/api-reference/data/get-a-people-enrichment-jobs-progress-and-rows) every few seconds:

```json theme={null}
{
  "jobId": "6a1f2c3d4e5f6a7b8c9d0e1f",
  "status": "completed",
  "total": 2,
  "processed": 2,
  "found": 1,
  "creditsCharged": 220,
  "rows": [
    {
      "index": 0,
      "status": "found",
      "email": { "value": "jane@stripe.com", "status": "valid", "type": "work" },
      "phone": { "value": "+14155550123" },
      "linkedinUrl": "linkedin.com/in/jane-doe",
      "creditsCharged": 220
    },
    {
      "index": 1,
      "status": "not_found",
      "email": null,
      "phone": null,
      "linkedinUrl": null,
      "creditsCharged": 0
    }
  ],
  "stoppedReason": null
}
```

| `status` | Means |
| - | - |
| `pending` | Accepted and waiting to start. |
| `processing` | Running. `rows` holds the people done so far. |
| `completed` | Every person has a row. |
| `stopped` | The balance ran out, with `stoppedReason: "INSUFFICIENT_CREDITS"`. The rows it finished are kept. It does not resume, so start a new job for the people without a row. |
| `failed` | Ended by an error. The rows it finished are kept. |

* Each row carries its `index` in `people`. Place rows by `index`: a person asked again after a busy provider can finish after the people sent behind them.
* A row's `status` is `found`, `not_found`, or `not_identifiable` when the person could not be looked up, which is free.
* A job of 100 people takes a few minutes. Its lookups use only what live requests leave of each provider's capacity, so it takes longer when providers are busy. A person no busy provider could be asked about yet is tried again every minute, for up to 15 minutes from the job's start, and then answered `not_found`.
* Polling is free and counts against your account reads (120 a minute), not the plan. The `jobId` works with this endpoint only: `GET /business/jobs/{jobId}` does not serve it.
* Jobs are kept for 7 days. After that the poll answers `404 ENRICHMENT_JOB_NOT_FOUND`.

## Send an idempotency key

Send an `X-Idempotency-Key` header, such as a UUID, on the calls that charge: email and phone lookups, verification and starting a job. The same key with the same body is charged once however often it is sent, so a request that timed out on your side can be sent again safely. A job start sent again with its key answers the job it started instead of starting another, and if the start answered `500`, sending it again with the same key queues the job it recorded. The same key with a different body is new work, and is charged.

## Errors

The data endpoints answer with the standard [error envelope](https://docs.fuseai.com/concepts/errors.md). The answers to handle:

| Status | Code | When |
| - | - | - |
| `402` | [`INSUFFICIENT_CREDITS`](https://docs.fuseai.com/concepts/errors#insufficient_credits) | The balance cannot pay for the call. Nothing was looked up or started. |
| `403` | [`API_NOT_ON_PLAN`](https://docs.fuseai.com/concepts/errors#api_not_on_plan) | The endpoint, or a kind a job's `find` asks for, is not on your key's usage plan. |
| `403` | [`INSUFFICIENT_SCOPE`](https://docs.fuseai.com/concepts/errors#insufficient_scope) | The key does not carry the `data` scope. |
| `404` | [`ENRICHMENT_JOB_NOT_FOUND`](https://docs.fuseai.com/concepts/errors#enrichment_job_not_found) | No job with that id for this account, or it is older than 7 days. |
| `422` | [`PERSON_NOT_IDENTIFIABLE`](https://docs.fuseai.com/concepts/errors#person_not_identifiable) | Neither a `linkedinUrl` nor a `name` with a company that resolves. Nothing was charged. |
| `422` | [`COMPANY_IDENTIFIER_INVALID`](https://docs.fuseai.com/concepts/errors#company_identifier_invalid) | `domain` is not a website domain, or `linkedinUrl` is not a LinkedIn company page. |
| `429` | [`RATE_LIMITED`](https://docs.fuseai.com/concepts/errors#rate_limited) | A per-minute or per-day limit of your plan is spent: wait `Retry-After` seconds. |
| `503` | [`UNEXPECTED_ERROR`](https://docs.fuseai.com/concepts/errors#unexpected_error) | A count failed, or your account started too many new people counts: retry in a minute. |
| `504` | [`UNEXPECTED_ERROR`](https://docs.fuseai.com/concepts/errors#unexpected_error) | A lookup or check ran out of time. Nothing was charged: retry. |

The counts also refuse what the searches refuse, such as a `query` that names no concrete criteria (`422 QUERY_FILTERS_INVALID`) or a saved search that does not exist (`404 SAVED_SEARCH_NOT_FOUND`). Each endpoint's page in the API reference lists every answer it gives, for example [Count people matching a search](https://docs.fuseai.com/api-reference/data/count-people-matching-a-search-free.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.