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

# Contacts API

> REST endpoints for creating, reading, updating, and deleting contact records in ParsaLink.

<div
  style={{ 
display: "inline-flex",
alignItems: "center",
gap: "8px",
background: "linear-gradient(90deg, #143D4E 0%, #1C758A 100%)",
color: "#FFFFFF",
fontSize: "12px",
fontWeight: "700",
marginBottom: "10px",
padding: "8px 14px",
letterSpacing: "0.08em",
textTransform: "uppercase",
borderRadius: "999px",
boxShadow: "0 8px 24px rgba(20, 61, 78, 0.18)"
}}
>
  Contacts
</div>

## Endpoints

<div
  style={{

border: "1px solid rgba(48, 200, 202, 0.22)",
borderRadius: "16px",
padding: "16px 18px",
marginBottom: "20px",
color: "#2C768D",
boxShadow: "0 10px 26px rgba(28, 117, 138, 0.08)"
}}
  className="dark:!bg-[#0B0B0B] dark:!border-gray-800 dark:!text-cyan-300 dark:shadow-none"
>
  Available endpoints for managing contact records.
</div>

<div
  style={{
background: "linear-gradient(135deg, rgba(20, 61, 78, 0.04) 0%, rgba(28, 117, 138, 0.08) 100%)",
border: "1px solid #CDD1D3",
borderRadius: "18px",
padding: "8px 12px 2px 12px",
marginBottom: "22px",
boxShadow: "0 10px 30px rgba(20, 61, 78, 0.06)"
}}
>
  | Method   | Path                  | Description      |
  | -------- | --------------------- | ---------------- |
  | `GET`    | `/api/contacts/`      | List contacts    |
  | `POST`   | `/api/contacts/`      | Create a contact |
  | `GET`    | `/api/contacts/{id}/` | Get a contact    |
  | `PATCH`  | `/api/contacts/{id}/` | Update a contact |
  | `DELETE` | `/api/contacts/{id}/` | Delete a contact |
</div>

***

<div
  style={{ 
display: "inline-flex",
alignItems: "center",
gap: "8px",
background: "rgba(48, 200, 202, 0.12)",
color: "#1C758A",
fontSize: "12px",
fontWeight: "700",
marginBottom: "10px",
padding: "8px 14px",
letterSpacing: "0.08em",
textTransform: "uppercase",
borderRadius: "999px",
border: "1px solid rgba(48, 200, 202, 0.28)"
}}
>
  Contacts
</div>

## List Contacts

```http theme={null}
GET /api/contacts/
Authorization: Bearer <token>
```

<div
  style={{

border: "1px solid rgba(48, 200, 202, 0.22)",
borderRadius: "16px",
padding: "16px 18px",
marginBottom: "20px",
color: "#2C768D",
boxShadow: "0 10px 26px rgba(28, 117, 138, 0.08)"
}}
  className="dark:!bg-[#0B0B0B] dark:!border-gray-800 dark:!text-cyan-300 dark:shadow-none"
>
  Retrieve a paginated list of contacts with filtering and search support.
</div>

**Query Parameters:**

<div
  style={{

border: "1px solid #CDD1D3",
borderRadius: "18px",
padding: "8px 12px 2px 12px",
marginBottom: "22px",
boxShadow: "0 10px 30px rgba(20, 61, 78, 0.05)"
}}
  className="dark:!bg-black dark:!border-gray-800 dark:shadow-none"
>
  | Parameter       | Type    | Description                                   |
  | --------------- | ------- | --------------------------------------------- |
  | `page`          | integer | Page number (default: 1)                      |
  | `limit`         | integer | Results per page (default: 20, max: 100)      |
  | `stage`         | string  | Filter by stage (e.g., `engaged`, `customer`) |
  | `assignedTo`    | string  | Filter by assigned user ID                    |
  | `search`        | string  | Search by name, email, or phone               |
  | `createdAfter`  | ISO8601 | Filter contacts created after this date       |
  | `createdBefore` | ISO8601 | Filter contacts created before this date      |
  | `tags`          | string  | Comma-separated tag names to filter by        |
</div>

**Response:**

```json theme={null}
{
  "count": 52,
  "next": "https://api.parsalink.io/api/contacts/?page=2",
  "previous": null,
  "results": [
    {
      "id": "cnt_01HX...",
      "firstName": "John",
      "lastName": "Smith",
      "email": "john@acme.com",
      "phone": "+15551234567",
      "title": "VP of Sales",
      "stage": "engaged",
      "accountId": "acc_01HX...",
      "accountName": "Acme Corp",
      "assignedTo": "usr_01HX...",
      "tags": ["enterprise", "warm"],
      "createdAt": "2025-01-15T10:30:00Z",
      "updatedAt": "2025-03-20T14:22:00Z"
    }
  ]
}
```

***

<div
  style={{ 
display: "inline-flex",
alignItems: "center",
gap: "8px",
background: "linear-gradient(90deg, #143D4E 0%, #1C758A 100%)",
color: "#FFFFFF",
fontSize: "12px",
fontWeight: "700",
marginBottom: "10px",
padding: "8px 14px",
letterSpacing: "0.08em",
textTransform: "uppercase",
borderRadius: "999px",
boxShadow: "0 8px 24px rgba(20, 61, 78, 0.18)"
}}
>
  Creation
</div>

## Create a Contact

```http theme={null}
POST /api/contacts/
Authorization: Bearer <token>
Content-Type: application/json
```

<div
  style={{

border: "1px solid rgba(48, 200, 202, 0.22)",
borderRadius: "16px",
padding: "16px 18px",
marginBottom: "20px",
color: "#2C768D",
boxShadow: "0 10px 26px rgba(28, 117, 138, 0.08)"
}}
  className="dark:!bg-[#0B0B0B] dark:!border-gray-800 dark:!text-cyan-300 dark:shadow-none"
>
  Create a new contact with profile, account linkage, and tags.
</div>

**Request body:**

```json theme={null}
{
  "firstName": "Sarah",
  "lastName": "Kim",
  "email": "sarah@techcorp.com",
  "phone": "+15559876543",
  "title": "Engineering Manager",
  "stage": "new",
  "accountId": "acc_01HX...",
  "assignedTo": "usr_01HX...",
  "tags": ["saas", "inbound"],
  "notes": "Met at SaaStr conference"
}
```

**Required fields:** `firstName` or `lastName` (at least one), `email`

**Response:** `201 Created` with the full contact object.

***

<div
  style={{ 
display: "inline-flex",
alignItems: "center",
gap: "8px",
background: "rgba(255, 111, 97, 0.12)",
color: "#FF6F61",
fontSize: "12px",
fontWeight: "700",
marginBottom: "10px",
padding: "8px 14px",
letterSpacing: "0.08em",
textTransform: "uppercase",
borderRadius: "999px",
border: "1px solid rgba(255, 111, 97, 0.24)"
}}
>
  Contact Details
</div>

## Get a Contact

```http theme={null}
GET /api/contacts/{id}/
Authorization: Bearer <token>
```

<div
  style={{

border: "1px solid rgba(48, 200, 202, 0.22)",
borderRadius: "16px",
padding: "16px 18px",
marginBottom: "20px",
color: "#2C768D",
boxShadow: "0 10px 26px rgba(28, 117, 138, 0.08)"
}}
  className="dark:!bg-[#0B0B0B] dark:!border-gray-800 dark:!text-cyan-300 dark:shadow-none"
>
  Retrieve full contact profile including ownership and activity flags.
</div>

**Response:**

```json theme={null}
{
  "id": "cnt_01HX...",
  "firstName": "Sarah",
  "lastName": "Kim",
  "email": "sarah@techcorp.com",
  "phone": "+15559876543",
  "title": "Engineering Manager",
  "stage": "new",
  "accountId": "acc_01HX...",
  "accountName": "TechCorp",
  "assignedTo": "usr_01HX...",
  "assignedToName": "Alex Johnson",
  "tags": ["saas", "inbound"],
  "emailOptOut": false,
  "emailBounced": false,
  "notes": "Met at SaaStr conference",
  "createdAt": "2025-03-01T09:00:00Z",
  "updatedAt": "2025-03-01T09:00:00Z"
}
```

***

<div
  style={{ 
display: "inline-flex",
alignItems: "center",
gap: "8px",
background: "rgba(48, 200, 202, 0.12)",
color: "#1C758A",
fontSize: "12px",
fontWeight: "700",
marginBottom: "10px",
padding: "8px 14px",
letterSpacing: "0.08em",
textTransform: "uppercase",
borderRadius: "999px",
border: "1px solid rgba(48, 200, 202, 0.28)"
}}
>
  Updates
</div>

## Update a Contact

Use `PATCH` to update specific fields without affecting the rest.

```http theme={null}
PATCH /api/contacts/{id}/
Authorization: Bearer <token>
Content-Type: application/json
```

<div
  style={{

border: "1px solid rgba(48, 200, 202, 0.22)",
borderRadius: "16px",
padding: "16px 18px",
marginBottom: "20px",
color: "#2C768D",
boxShadow: "0 10px 26px rgba(28, 117, 138, 0.08)"
}}
  className="dark:!bg-[#0B0B0B] dark:!border-gray-800 dark:!text-cyan-300 dark:shadow-none"
>
  Update contact attributes such as stage, owner, or tags.
</div>

**Request body (any subset of writable fields):**

```json theme={null}
{
  "stage": "engaged",
  "assignedTo": "usr_02HX...",
  "tags": ["saas", "inbound", "qualified"]
}
```

**Response:** `200 OK` with the updated contact object.

***

<div
  style={{ 
display: "inline-flex",
alignItems: "center",
gap: "8px",
background: "rgba(255, 111, 97, 0.12)",
color: "#FF6F61",
fontSize: "12px",
fontWeight: "700",
marginBottom: "10px",
padding: "8px 14px",
letterSpacing: "0.08em",
textTransform: "uppercase",
borderRadius: "999px",
border: "1px solid rgba(255, 111, 97, 0.24)"
}}
>
  Deletion
</div>

## Delete a Contact

```http theme={null}
DELETE /api/contacts/{id}/
Authorization: Bearer <token>
```

<div
  style={{

border: "1px solid rgba(48, 200, 202, 0.22)",
borderRadius: "16px",
padding: "16px 18px",
marginBottom: "20px",
color: "#2C768D",
boxShadow: "0 10px 26px rgba(28, 117, 138, 0.08)"
}}
  className="dark:!bg-[#0B0B0B] dark:!border-gray-800 dark:!text-cyan-300 dark:shadow-none"
>
  Permanently remove a contact and its associated records.
</div>

**Response:** `204 No Content`

Deleting a contact removes the record and all associated notes and tasks. Activities logged on the contact's timeline are retained in the audit log but no longer accessible through the contact record.

***

<div
  style={{ 
display: "inline-flex",
alignItems: "center",
gap: "8px",
background: "rgba(48, 200, 202, 0.12)",
color: "#1C758A",
fontSize: "12px",
fontWeight: "700",
marginBottom: "10px",
padding: "8px 14px",
letterSpacing: "0.08em",
textTransform: "uppercase",
borderRadius: "999px",
border: "1px solid rgba(48, 200, 202, 0.28)"
}}
>
  Stages
</div>

## Contact Stage Values

<div
  style={{
 border: "1px solid #CDD1D3",
borderRadius: "18px",
padding: "8px 12px 2px 12px",
marginBottom: "22px",
boxShadow: "0 10px 30px rgba(20, 61, 78, 0.05)"
}}
  className="dark:!bg-black dark:!border-gray-800 dark:shadow-none"
>
  | Value           | Label         |
  | --------------- | ------------- |
  | `new`           | New           |
  | `contacted`     | Contacted     |
  | `engaged`       | Engaged       |
  | `proposal_sent` | Proposal Sent |
  | `negotiation`   | Negotiation   |
  | `customer`      | Customer      |
  | `inactive`      | Inactive      |
</div>

***

## Async CSV Import

For bulk imports of more than a few hundred rows, use the async import endpoints. The synchronous `unified-import` endpoint stays available for small CSVs and other file types (vCard, image, PDF) but will gateway-time-out on large CSVs.

### Start an Import

```
POST /api/crm/contacts/import/start/
Content-Type: multipart/form-data
```

**FormData fields**

| Field            | Required | Description                                                     |
| ---------------- | -------- | --------------------------------------------------------------- |
| `file`           | yes      | The CSV file (`.csv` only)                                      |
| `source_uid`     | no       | UID of a `Source` to stamp on every created contact             |
| `field_mappings` | no       | JSON string mapping unknown CSV column headers to schema fields |

**Limits**

* 10 MB CSV body cap
* One job per request
* File must be UTF-8 (falls back to latin-1 if UTF-8 decode fails)

**Success response (`202 Accepted`)**

```json theme={null}
{
  "uid": "fd8b9b8e-…",
  "status": "queued",
  "filename": "leads.csv"
}
```

The job is queued for background processing. Poll the detail endpoint to follow progress.

### Poll a Job

```
GET /api/crm/contacts/import-jobs/{job_uid}/
```

**Success response (`200`)**

```json theme={null}
{
  "uid": "fd8b9b8e-…",
  "filename": "leads.csv",
  "status": "complete",
  "progressPercent": 100,
  "totalRows": 250,
  "processedRows": 250,
  "successCount": 247,
  "errorCount": 3,
  "errors": [
    { "row": 14, "message": "Invalid email format" }
  ],
  "duplicates": [ … ],
  "errorMessage": "",
  "sourceName": "Trade Show",
  "createdAt": "2026-06-08T10:01:16Z",
  "startedAt": "2026-06-08T10:01:18Z",
  "finishedAt": "2026-06-08T10:02:04Z"
}
```

**Status values**

| Status       | Meaning                                           |
| ------------ | ------------------------------------------------- |
| `queued`     | In the Celery queue, hasn't started               |
| `processing` | Worker is currently importing rows                |
| `complete`   | Finished — check `successCount` and `errorCount`  |
| `failed`     | Threw an error; `errorMessage` carries the reason |

Poll every 1–2 seconds during active runs. Stop polling once `status` is `complete` or `failed`.

### List Recent Jobs

```
GET /api/crm/contacts/import-jobs/
```

Returns the last 50 jobs for the authenticated user's tenant, most recent first. Same shape as the detail endpoint, but the response is an array.

Useful for surfacing a "Recent Imports" panel in your UI without needing to track job UIDs yourself.
