API reference
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
curl -s "https://api.closefinch.com/v1/enrichment/calls" \
-H "Authorization: Bearer $RA_API_KEY"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)
curl -s "https://api.closefinch.com/v1/enrichment/policy" \
-H "Authorization: Bearer $RA_API_KEY"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
curl -s -X PUT "https://api.closefinch.com/v1/enrichment/policy" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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)
curl -s "https://api.closefinch.com/v1/enrichment/runs" \
-H "Authorization: Bearer $RA_API_KEY"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
curl -s -X POST "https://api.closefinch.com/v1/enrichment/runs" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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
curl -s "https://api.closefinch.com/v1/enrichment/runs/$ID" \
-H "Authorization: Bearer $RA_API_KEY"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
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 '{}'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)
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 '{}'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
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 '{}'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
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 '{}'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
curl -s -X POST "https://api.closefinch.com/v1/phones/$ID/lookup" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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
curl -s -X POST "https://api.closefinch.com/v1/phones/$ID/outcomes" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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
curl -s -X POST "https://api.closefinch.com/v1/phones/$ID/revive" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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.
curl -s "https://api.closefinch.com/v1/records/$ID/best-numbers" \
-H "Authorization: Bearer $RA_API_KEY"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)
curl -s -X POST "https://api.closefinch.com/v1/records/$ID/enrich" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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
curl -s "https://api.closefinch.com/v1/records/$ID/enrichment" \
-H "Authorization: Bearer $RA_API_KEY"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
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 '{}'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
curl -s "https://api.closefinch.com/v1/sheets" \
-H "Authorization: Bearer $RA_API_KEY"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
curl -s -X POST "https://api.closefinch.com/v1/sheets" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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)
curl -s -X DELETE "https://api.closefinch.com/v1/sheets/$ID" \
-H "Authorization: Bearer $RA_API_KEY"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
curl -s "https://api.closefinch.com/v1/sheets/$ID" \
-H "Authorization: Bearer $RA_API_KEY"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)
curl -s -X PATCH "https://api.closefinch.com/v1/sheets/$ID/cells" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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)
curl -s -X POST "https://api.closefinch.com/v1/sheets/$ID/columns" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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
curl -s -X DELETE "https://api.closefinch.com/v1/sheets/$ID/columns/$COLUMN" \
-H "Authorization: Bearer $RA_API_KEY"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)
curl -s "https://api.closefinch.com/v1/sheets/$ID/export" \
-H "Authorization: Bearer $RA_API_KEY"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
curl -s -X POST "https://api.closefinch.com/v1/sheets/$ID/more" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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)
curl -s -X POST "https://api.closefinch.com/v1/sheets/$ID/push" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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
curl -s "https://api.closefinch.com/v1/sheets/$ID/rows" \
-H "Authorization: Bearer $RA_API_KEY"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
curl -s -X POST "https://api.closefinch.com/v1/sheets/$ID/runs" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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
curl -s -X POST "https://api.closefinch.com/v1/sheets/csv" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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.
curl -s -X POST "https://api.closefinch.com/v1/sheets/vibe" \
-H "Authorization: Bearer $RA_API_KEY" \
-H "content-type: application/json" \
-d '{}'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. All resources: API reference.