# API: Enrichment

| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | `/v1/enrichment/calls` | The enrichment ledger: every step (vendor call, cached answer, in-house step, skip) with its fields, outcome, cost and approver | `prospecting:read` |
| GET | `/v1/enrichment/policy` | The enrichment policy: the waterfall order per field, allowed vendors and fields, caps (per record, run and month), the approval threshold, cache days and prices; each paid vendor's state (keys are never shown) | `prospecting:read` |
| PUT | `/v1/enrichment/policy` | Change the enrichment policy (owner): waterfall order per field, vendors and fields allowed, caps, approval threshold, cache days, prices | `settings:write` |
| GET | `/v1/enrichment/runs` | Enrichment runs, newest first (including runs waiting for an owner's approval) | `prospecting:read` |
| POST | `/v1/enrichment/runs` | Set up a bulk enrichment run over records or a list (up to 10,000). Returns the cost estimate; nothing runs until it's started | `prospecting:write` |
| GET | `/v1/enrichment/runs/:id` | A run: status, estimate, budget and spend, counts, approval | `prospecting:read` |
| POST | `/v1/enrichment/runs/:id/approve` | Approve a run's paid enrichment (owner): it's queued with a budget covering its upper estimate | `settings:write` |
| POST | `/v1/enrichment/runs/:id/reject` | Decline a run's paid enrichment (owner) | `settings:write` |
| POST | `/v1/enrichment/runs/:id/start` | Start a run: queued, or waiting for an owner's approval when the estimate passes the approval threshold or the monthly cap | `prospecting:write` |
| POST | `/v1/enrichment/runs/:id/stop` | Stop a run | `prospecting:write` |
| POST | `/v1/phones/:id/lookup` | Queue a line lookup (Telnyx Number Lookup: line type and validity). Off until a key is set; a lookup within 30 days isn't billed again | `contacts:write` |
| POST | `/v1/phones/:id/outcomes` | Log a call's outcome on a number (connected, voicemail, no answer, busy, disconnected, wrong number); the ranking and dead flag are recomputed | `contacts:write` |
| POST | `/v1/phones/:id/revive` | Clear a dead flag: earlier outcomes stop counting and the state goes back to the format check | `contacts:write` |
| GET | `/v1/records/:id/best-numbers` | The record's numbers ranked by how likely each is to connect (line type, state, source, its own call outcomes), each with the reason and last outcomes; dead numbers flagged and never ranked. Recomputed on read. | `prospecting:read` |
| POST | `/v1/records/:id/enrich` | Enrich this record's empty fields in the background (a one-record run; it waits for an owner when the estimate passes the caps) | `prospecting:write` |
| GET | `/v1/records/:id/enrichment` | A contact's, lead's or account's enrichment: each enriched field's provenance (provider, date, confidence, cost, source), guesses and not-found fields, which fields are empty, and the latest steps | `prospecting:read` |
| POST | `/v1/records/:id/enrichment/preview` | What enriching this record's empty fields could cost (no call is made): the vendors it would reach, cached answers, at most and likely cost, and whether an owner must approve | `prospecting:read` |
| GET | `/v1/sheets` | Sheets (list workspaces), newest first | `prospecting:read` |
| POST | `/v1/sheets` | A sheet over a list or a search: the first 500 rows now, up to 10,000 in the background | `prospecting:write` |
| DELETE | `/v1/sheets/:id` | Delete a sheet (its rows and cells; CRM records stay) | `prospecting:write` |
| GET | `/v1/sheets/:id` | A sheet with its columns and its latest runs | `prospecting:read` |
| PATCH | `/v1/sheets/:id/cells` | Set a cell by hand (audited with its earlier value) | `prospecting:write` |
| POST | `/v1/sheets/:id/columns` | Add a column: a CRM field, an enrichment field (filled through the waterfall), or an AI column (name, instruction, output type) | `prospecting:write` |
| DELETE | `/v1/sheets/:id/columns/:column` | Remove a column and its cells | `prospecting:write` |
| GET | `/v1/sheets/:id/export` | The sheet as CSV (each column with its source) | `prospecting:read` |
| POST | `/v1/sheets/:id/more` | Load more: the next 10,000 matches of the sheet's list or search, in the background | `prospecting:write` |
| POST | `/v1/sheets/:id/push` | Push to a CRM list: matched rows join a new static list; with create_unmatched, unmatched rows become records first (at most 2,000) | `prospecting:write` |
| GET | `/v1/sheets/:id/rows` | A page of rows (up to 200) with their CRM fields and each cell's value, status and source | `prospecting:read` |
| POST | `/v1/sheets/:id/runs` | Set up a column run (enrichment and AI columns over all rows, rows not yet filled, or unmatched CSV rows) with its cost estimate; start it with /v1/enrichment/runs/:id/start | `prospecting:write` |
| POST | `/v1/sheets/csv` | A sheet from a CSV (up to 10,000 rows): rows kept as uploaded, matched to the CRM by email, domain or LinkedIn in a job (match rate reported, unmatched rows kept); asked-for columns are then enriched for the unmatched rows as a run | `prospecting:write` |
| POST | `/v1/sheets/vibe` | "Vibe campaign" (our reading, marked as an assumption): describe a campaign; get a sheet from the search it compiles to, with suggested columns (not run until started). Nothing is sent. | `prospecting:write` |

## Examples

### GET /v1/enrichment/calls

The enrichment ledger: every step (vendor call, cached answer, in-house step, skip) with its fields, outcome, cost and approver

```bash
curl -s "https://api.closefinch.com/v1/enrichment/calls" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/enrichment/calls`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### GET /v1/enrichment/policy

The enrichment policy: the waterfall order per field, allowed vendors and fields, caps (per record, run and month), the approval threshold, cache days and prices; each paid vendor's state (keys are never shown)

```bash
curl -s "https://api.closefinch.com/v1/enrichment/policy" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/enrichment/policy`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### PUT /v1/enrichment/policy

Change the enrichment policy (owner): waterfall order per field, vendors and fields allowed, caps, approval threshold, cache days, prices

```bash
curl -s -X PUT "https://api.closefinch.com/v1/enrichment/policy" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/enrichment/policy`, {
  method: "PUT",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### GET /v1/enrichment/runs

Enrichment runs, newest first (including runs waiting for an owner's approval)

```bash
curl -s "https://api.closefinch.com/v1/enrichment/runs" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/enrichment/runs`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### POST /v1/enrichment/runs

Set up a bulk enrichment run over records or a list (up to 10,000). Returns the cost estimate; nothing runs until it's started

```bash
curl -s -X POST "https://api.closefinch.com/v1/enrichment/runs" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/enrichment/runs`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### GET /v1/enrichment/runs/$ID

A run: status, estimate, budget and spend, counts, approval

```bash
curl -s "https://api.closefinch.com/v1/enrichment/runs/$ID" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/enrichment/runs/${id}`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### POST /v1/enrichment/runs/$ID/approve

Approve a run's paid enrichment (owner): it's queued with a budget covering its upper estimate

```bash
curl -s -X POST "https://api.closefinch.com/v1/enrichment/runs/$ID/approve" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/enrichment/runs/${id}/approve`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### POST /v1/enrichment/runs/$ID/reject

Decline a run's paid enrichment (owner)

```bash
curl -s -X POST "https://api.closefinch.com/v1/enrichment/runs/$ID/reject" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/enrichment/runs/${id}/reject`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### POST /v1/enrichment/runs/$ID/start

Start a run: queued, or waiting for an owner's approval when the estimate passes the approval threshold or the monthly cap

```bash
curl -s -X POST "https://api.closefinch.com/v1/enrichment/runs/$ID/start" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/enrichment/runs/${id}/start`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### POST /v1/enrichment/runs/$ID/stop

Stop a run

```bash
curl -s -X POST "https://api.closefinch.com/v1/enrichment/runs/$ID/stop" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/enrichment/runs/${id}/stop`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### POST /v1/phones/$ID/lookup

Queue a line lookup (Telnyx Number Lookup: line type and validity). Off until a key is set; a lookup within 30 days isn't billed again

```bash
curl -s -X POST "https://api.closefinch.com/v1/phones/$ID/lookup" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/phones/${id}/lookup`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### POST /v1/phones/$ID/outcomes

Log a call's outcome on a number (connected, voicemail, no answer, busy, disconnected, wrong number); the ranking and dead flag are recomputed

```bash
curl -s -X POST "https://api.closefinch.com/v1/phones/$ID/outcomes" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/phones/${id}/outcomes`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### POST /v1/phones/$ID/revive

Clear a dead flag: earlier outcomes stop counting and the state goes back to the format check

```bash
curl -s -X POST "https://api.closefinch.com/v1/phones/$ID/revive" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/phones/${id}/revive`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### GET /v1/records/$ID/best-numbers

The record's numbers ranked by how likely each is to connect (line type, state, source, its own call outcomes), each with the reason and last outcomes; dead numbers flagged and never ranked. Recomputed on read.

```bash
curl -s "https://api.closefinch.com/v1/records/$ID/best-numbers" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/records/${id}/best-numbers`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### POST /v1/records/$ID/enrich

Enrich this record's empty fields in the background (a one-record run; it waits for an owner when the estimate passes the caps)

```bash
curl -s -X POST "https://api.closefinch.com/v1/records/$ID/enrich" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/records/${id}/enrich`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### GET /v1/records/$ID/enrichment

A contact's, lead's or account's enrichment: each enriched field's provenance (provider, date, confidence, cost, source), guesses and not-found fields, which fields are empty, and the latest steps

```bash
curl -s "https://api.closefinch.com/v1/records/$ID/enrichment" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/records/${id}/enrichment`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### POST /v1/records/$ID/enrichment/preview

What enriching this record's empty fields could cost (no call is made): the vendors it would reach, cached answers, at most and likely cost, and whether an owner must approve

```bash
curl -s -X POST "https://api.closefinch.com/v1/records/$ID/enrichment/preview" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/records/${id}/enrichment/preview`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### GET /v1/sheets

Sheets (list workspaces), newest first

```bash
curl -s "https://api.closefinch.com/v1/sheets" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### POST /v1/sheets

A sheet over a list or a search: the first 500 rows now, up to 10,000 in the background

```bash
curl -s -X POST "https://api.closefinch.com/v1/sheets" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### DELETE /v1/sheets/$ID

Delete a sheet (its rows and cells; CRM records stay)

```bash
curl -s -X DELETE "https://api.closefinch.com/v1/sheets/$ID" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/${id}`, {
  method: "DELETE",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### GET /v1/sheets/$ID

A sheet with its columns and its latest runs

```bash
curl -s "https://api.closefinch.com/v1/sheets/$ID" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/${id}`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### PATCH /v1/sheets/$ID/cells

Set a cell by hand (audited with its earlier value)

```bash
curl -s -X PATCH "https://api.closefinch.com/v1/sheets/$ID/cells" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/${id}/cells`, {
  method: "PATCH",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### POST /v1/sheets/$ID/columns

Add a column: a CRM field, an enrichment field (filled through the waterfall), or an AI column (name, instruction, output type)

```bash
curl -s -X POST "https://api.closefinch.com/v1/sheets/$ID/columns" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/${id}/columns`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### DELETE /v1/sheets/$ID/columns/$COLUMN

Remove a column and its cells

```bash
curl -s -X DELETE "https://api.closefinch.com/v1/sheets/$ID/columns/$COLUMN" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/${id}/columns/${column}`, {
  method: "DELETE",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### GET /v1/sheets/$ID/export

The sheet as CSV (each column with its source)

```bash
curl -s "https://api.closefinch.com/v1/sheets/$ID/export" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/${id}/export`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### POST /v1/sheets/$ID/more

Load more: the next 10,000 matches of the sheet's list or search, in the background

```bash
curl -s -X POST "https://api.closefinch.com/v1/sheets/$ID/more" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/${id}/more`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### POST /v1/sheets/$ID/push

Push to a CRM list: matched rows join a new static list; with create_unmatched, unmatched rows become records first (at most 2,000)

```bash
curl -s -X POST "https://api.closefinch.com/v1/sheets/$ID/push" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/${id}/push`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### GET /v1/sheets/$ID/rows

A page of rows (up to 200) with their CRM fields and each cell's value, status and source

```bash
curl -s "https://api.closefinch.com/v1/sheets/$ID/rows" \
  -H "Authorization: Bearer $RA_API_KEY"
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/${id}/rows`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
const data = await res.json();
```

### POST /v1/sheets/$ID/runs

Set up a column run (enrichment and AI columns over all rows, rows not yet filled, or unmatched CSV rows) with its cost estimate; start it with /v1/enrichment/runs/:id/start

```bash
curl -s -X POST "https://api.closefinch.com/v1/sheets/$ID/runs" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/${id}/runs`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### POST /v1/sheets/csv

A sheet from a CSV (up to 10,000 rows): rows kept as uploaded, matched to the CRM by email, domain or LinkedIn in a job (match rate reported, unmatched rows kept); asked-for columns are then enriched for the unmatched rows as a run

```bash
curl -s -X POST "https://api.closefinch.com/v1/sheets/csv" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/csv`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

### POST /v1/sheets/vibe

"Vibe campaign" (our reading, marked as an assumption): describe a campaign; get a sheet from the search it compiles to, with suggested columns (not run until started). Nothing is sent.

```bash
curl -s -X POST "https://api.closefinch.com/v1/sheets/vibe" \
  -H "Authorization: Bearer $RA_API_KEY" \
  -H "content-type: application/json" \
  -d '{}'
```

```js
const res = await fetch(`https://api.closefinch.com/v1/sheets/vibe`, {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({}), // fields: see the OpenAPI schema
});
const data = await res.json();
```

Request and response schemas: [https://www.closefinch.com/openapi.json](https://www.closefinch.com/openapi.json). All resources: [API reference](https://www.closefinch.com/docs/api).
