# API: Contacts

| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | `/v1/contacts` | List contacts (filters: q, owner_id, list_id, linked_to, dismissed, filter[field]=value, filter[field][gte\|lte\|gt\|lt\|in\|exists]=value) | `contacts:read` |
| POST | `/v1/contacts` | Create a contact | `contacts:write` |
| DELETE | `/v1/contacts/:id` | Archive a record (restore with POST …/restore) | `contacts:write` |
| GET | `/v1/contacts/:id` | Get a record (as_of=ISO time for its values at that moment). A merged record answers 301 with the survivor's id. | `contacts:read` |
| PATCH | `/v1/contacts/:id` | Update values or owner. A merged record answers 410. Pass version for optimistic concurrency. | `contacts:write` |
| POST | `/v1/contacts/:id/dismiss` | Dismiss a noise record so it stops resurfacing (undo with dismissed: false) | `contacts:write` |
| GET | `/v1/contacts/:id/history` | Every change to this record, newest first, with field-level before and after. Values that came from an item now private (or metadata-only) to someone else show as null with redacted: true. | `contacts:read` |
| POST | `/v1/contacts/:id/merge` | Merge other records into this one. They redirect here afterwards. | `contacts:write` |
| GET | `/v1/contacts/:id/provenance` | Where each current value came from (user, API, import, activity, agent or sync). `link` points at the activity, import row or record behind it. | `contacts:read` |
| POST | `/v1/contacts/:id/restore` | Restore an archived record | `contacts:write` |
| GET | `/v1/contacts/:id/timeline` | Activity about this record, newest first (deleted-at-source items hidden) | `activities:read` |
| POST | `/v1/contacts/analyze-write` | Before creating or updating: does this record already exist? Returns create, update (with the record to update) or review, with every match (same email, domain, phone, LinkedIn, same or similar name), rows of a batch that repeat each other, and for updates the values that would be overwritten. Agents' creates run this check and are refused when the record exists. | `contacts:read` |
| POST | `/v1/contacts/bulk` | Create up to 25 records. Each row_key (yours alone) is idempotent: the same key and data replays the record it created; the same key with different data is a conflict. | `contacts:write` |
| POST | `/v1/contacts/dismiss` | Dismiss (or undo with dismissed: false) many noise records at once | `contacts:write` |
| POST | `/v1/contacts/duplicates` | Check for existing records before creating or updating (same email, domain, phone, LinkedIn or name) | `contacts:read` |
| GET | `/v1/contacts/possible-duplicates` | Groups of records that look like the same thing (shared email, domain, phone, LinkedIn or exact name), to review and merge | `contacts:read` |
| GET | `/v1/contacts/schema` | JSON Schemas for create and update bodies, including this organization's custom fields | `contacts:read` |

## Examples

### GET /v1/contacts

List contacts (filters: q, owner_id, list_id, linked_to, dismissed, filter[field]=value, filter[field][gte|lte|gt|lt|in|exists]=value)

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

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

### POST /v1/contacts

Create a contact

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

```js
const res = await fetch(`https://api.closefinch.com/v1/contacts`, {
  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/contacts/$ID

Archive a record (restore with POST …/restore)

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

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

### GET /v1/contacts/$ID

Get a record (as_of=ISO time for its values at that moment). A merged record answers 301 with the survivor's id.

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

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

### PATCH /v1/contacts/$ID

Update values or owner. A merged record answers 410. Pass version for optimistic concurrency.

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

```js
const res = await fetch(`https://api.closefinch.com/v1/contacts/${id}`, {
  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/contacts/$ID/dismiss

Dismiss a noise record so it stops resurfacing (undo with dismissed: false)

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

```js
const res = await fetch(`https://api.closefinch.com/v1/contacts/${id}/dismiss`, {
  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/contacts/$ID/history

Every change to this record, newest first, with field-level before and after. Values that came from an item now private (or metadata-only) to someone else show as null with redacted: true.

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

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

### POST /v1/contacts/$ID/merge

Merge other records into this one. They redirect here afterwards.

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

```js
const res = await fetch(`https://api.closefinch.com/v1/contacts/${id}/merge`, {
  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/contacts/$ID/provenance

Where each current value came from (user, API, import, activity, agent or sync). `link` points at the activity, import row or record behind it.

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

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

### POST /v1/contacts/$ID/restore

Restore an archived record

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

```js
const res = await fetch(`https://api.closefinch.com/v1/contacts/${id}/restore`, {
  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/contacts/$ID/timeline

Activity about this record, newest first (deleted-at-source items hidden)

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

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

### POST /v1/contacts/analyze-write

Before creating or updating: does this record already exist? Returns create, update (with the record to update) or review, with every match (same email, domain, phone, LinkedIn, same or similar name), rows of a batch that repeat each other, and for updates the values that would be overwritten. Agents' creates run this check and are refused when the record exists.

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

```js
const res = await fetch(`https://api.closefinch.com/v1/contacts/analyze-write`, {
  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/contacts/bulk

Create up to 25 records. Each row_key (yours alone) is idempotent: the same key and data replays the record it created; the same key with different data is a conflict.

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

```js
const res = await fetch(`https://api.closefinch.com/v1/contacts/bulk`, {
  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/contacts/dismiss

Dismiss (or undo with dismissed: false) many noise records at once

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

```js
const res = await fetch(`https://api.closefinch.com/v1/contacts/dismiss`, {
  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/contacts/duplicates

Check for existing records before creating or updating (same email, domain, phone, LinkedIn or name)

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

```js
const res = await fetch(`https://api.closefinch.com/v1/contacts/duplicates`, {
  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/contacts/possible-duplicates

Groups of records that look like the same thing (shared email, domain, phone, LinkedIn or exact name), to review and merge

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

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

### GET /v1/contacts/schema

JSON Schemas for create and update bodies, including this organization's custom fields

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

```js
const res = await fetch(`https://api.closefinch.com/v1/contacts/schema`, {
  method: "GET",
  headers: { authorization: `Bearer ${process.env.RA_API_KEY}` },
});
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).
