# Contacts API

> REST endpoints for contacts — request and response reference with examples.

<!-- Source: https://docs.actuallycare.com/api/reference/contacts -->

Your wider address book — vendors, lenders, other agents, and everyone else in your sphere.

Base URL: `https://api.actuallycare.com/v1` · Errors use the [standard envelope](/api/reference#error-responses) — see [Errors](/api/errors).

## List contacts

```http
GET /v1/contacts
```

Returns a paginated list of contacts visible to the authenticated user, with aggregate stats by status. Supports fuzzy search across name and email.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `search` | query | string | No | Fuzzy search across full name, first name, last name, and email |
| `type` | query | string | No | Filter by contact type |
| `archived` | query | boolean | No | Show archived contacts instead of active ones · Default: `false` |
| `page` | query | integer | No | Page number for pagination · Default: `1` · Min: 1 |
| `limit` | query | integer | No | Number of items per page (default 25; GET /escrows overrides this to 20 at the controller level) · Default: `25` · Max: 100 · Min: 1 |

### Example request

```bash title="cURL"
curl "https://api.actuallycare.com/v1/contacts" \
  -H "Authorization: Bearer YOUR_JWT"
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/contacts", {
  headers: {
    "Authorization": "Bearer YOUR_JWT",
  },
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.actuallycare.com/v1/contacts",
    headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": {
    "contacts": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "first_name": "Jordan",
        "last_name": "Lee",
        "full_name": "example full name",
        "email": "agent@example.com",
        "phone": "+1 661 555 0123",
        "contact_type": "buyer",
        "contact_status": "example contact status",
        "company": "example company",
        "license_number": "example license number",
        "street_address": "example street address",
        "city": "Bakersfield",
        "state": "CA",
        "zip_code": "93301",
        "notes": "example notes",
        "tags": [
          "example tags"
        ],
        "is_archived": true,
        "archived_at": "2026-07-15T14:32:10.000Z",
        "created_at": "2026-07-15T14:32:10.000Z",
        "updated_at": "2026-07-15T14:32:10.000Z"
      }
    ],
    "stats": {},
    "meta": {
      "total": 150,
      "page": 1,
      "limit": 20,
      "totalPages": 8,
      "hasMore": true
    }
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Paginated contact list with stats |
| `400` | Invalid request data |
| `401` | Authentication required |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Create a contact

```http
POST /v1/contacts
```

Adds a contact to the authenticated user's database. First name and contact type are required.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `first_name` | string | Yes | Contact's first name |
| `last_name` | string | No | Contact's last name |
| `contact_type` | enum | Yes | Role this contact plays in transactions (e.g. buyer, seller, lender, inspector) · One of: `buyer`, `seller`, `agent`, `lender`, `title_officer`, `inspector`, `appraiser`, `contractor`, `client`, `vendor`, `other` |
| `email` | string | No | Contact's email address · Format: email |
| `phone` | string | No | Contact's phone number |
| `company` | string | No | Company or organization the contact belongs to |
| `license_number` | string | No | Professional license number (e.g. for agents, lenders, inspectors) |
| `street_address` | string | No | Street address |
| `city` | string | No | City name |
| `state` | string | No | Two-letter state code |
| `zip_code` | string | No | ZIP code |
| `notes` | string | No | Free-form notes about the contact |
| `tags` | array of strings | No | Free-form labels for grouping and filtering contacts |

### Example request

```bash title="cURL"
curl -X POST "https://api.actuallycare.com/v1/contacts" \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Jordan",
    "contact_type": "buyer"
  }'
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/contacts", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_JWT",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "first_name": "Jordan",
    "contact_type": "buyer"
  }),
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.actuallycare.com/v1/contacts",
    headers={"Authorization": "Bearer YOUR_JWT"},
    json={
        "first_name": "Jordan",
        "contact_type": "buyer"
    },
)
data = resp.json()
```

### Example response (201)

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Jordan",
    "last_name": "Lee",
    "full_name": "example full name",
    "email": "agent@example.com",
    "phone": "+1 661 555 0123",
    "contact_type": "buyer",
    "contact_status": "example contact status",
    "company": "example company",
    "license_number": "example license number",
    "street_address": "example street address",
    "city": "Bakersfield",
    "state": "CA",
    "zip_code": "93301",
    "notes": "example notes",
    "tags": [
      "example tags"
    ],
    "is_archived": true,
    "archived_at": "2026-07-15T14:32:10.000Z",
    "created_at": "2026-07-15T14:32:10.000Z",
    "updated_at": "2026-07-15T14:32:10.000Z"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Contact created |
| `400` | Invalid request data |
| `401` | Authentication required |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Search contacts

```http
GET /v1/contacts/search
```

Searches the authenticated user's contacts by name, email, or role. Returns a flat array (not paginated) capped at the requested limit.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | query | string | No | Case-insensitive substring match on full name |
| `email` | query | string | No | Case-insensitive substring match on email |
| `role` | query | string | No | Filter by assigned contact role name |
| `limit` | query | integer | No | Default: `50` · Max: 100 · Min: 1 |

### Example request

```bash title="cURL"
curl "https://api.actuallycare.com/v1/contacts/search" \
  -H "Authorization: Bearer YOUR_JWT"
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/contacts/search", {
  headers: {
    "Authorization": "Bearer YOUR_JWT",
  },
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.actuallycare.com/v1/contacts/search",
    headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "first_name": "Jordan",
      "last_name": "Lee",
      "full_name": "example full name",
      "email": "agent@example.com",
      "phone": "+1 661 555 0123",
      "contact_type": "buyer",
      "contact_status": "example contact status",
      "company": "example company",
      "license_number": "example license number",
      "street_address": "example street address",
      "city": "Bakersfield",
      "state": "CA",
      "zip_code": "93301",
      "notes": "example notes",
      "tags": [
        "example tags"
      ],
      "is_archived": true,
      "archived_at": "2026-07-15T14:32:10.000Z",
      "created_at": "2026-07-15T14:32:10.000Z",
      "updated_at": "2026-07-15T14:32:10.000Z"
    }
  ],
  "count": 3
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Matching contacts |
| `400` | Invalid request data |
| `401` | Authentication required |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Get a contact

```http
GET /v1/contacts/{id}
```

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes | Unique identifier (UUID) · Format: uuid |

### Example request

```bash title="cURL"
curl "https://api.actuallycare.com/v1/contacts/:id" \
  -H "Authorization: Bearer YOUR_JWT"
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/contacts/:id", {
  headers: {
    "Authorization": "Bearer YOUR_JWT",
  },
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.actuallycare.com/v1/contacts/:id",
    headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Jordan",
    "last_name": "Lee",
    "full_name": "example full name",
    "email": "agent@example.com",
    "phone": "+1 661 555 0123",
    "contact_type": "buyer",
    "contact_status": "example contact status",
    "company": "example company",
    "license_number": "example license number",
    "street_address": "example street address",
    "city": "Bakersfield",
    "state": "CA",
    "zip_code": "93301",
    "notes": "example notes",
    "tags": [
      "example tags"
    ],
    "is_archived": true,
    "archived_at": "2026-07-15T14:32:10.000Z",
    "created_at": "2026-07-15T14:32:10.000Z",
    "updated_at": "2026-07-15T14:32:10.000Z"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The contact |
| `401` | Authentication required |
| `404` | Resource not found |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Update a contact

```http
PUT /v1/contacts/{id}
```

Updates contact fields. All fields are optional; only the fields you send are changed.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes | Unique identifier (UUID) · Format: uuid |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `first_name` | string | No | Contact's first name |
| `last_name` | string | No | Contact's last name |
| `email` | string | No | Contact's email address · Format: email |
| `phone` | string | No | Contact's phone number |
| `contact_type` | string | No | Role this contact plays in transactions (e.g. buyer, seller, lender, inspector) |
| `contact_status` | string | No | Workflow status of the contact (free-form, e.g. active, lead, client, past_client) |
| `company` | string | No | Company or organization the contact belongs to |
| `street_address` | string | No | Street address |
| `city` | string | No | City name |
| `state` | string | No | Two-letter state code |
| `zip_code` | string | No | ZIP code |
| `notes` | string | No | Free-form notes about the contact |
| `tags` | array of strings | No | Free-form labels for grouping and filtering contacts |

### Example request

```bash title="cURL"
curl -X PUT "https://api.actuallycare.com/v1/contacts/:id" \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Jordan",
    "last_name": "Lee",
    "email": "agent@example.com",
    "phone": "+1 661 555 0123",
    "contact_type": "your-contact-type",
    "contact_status": "your-contact-status",
    "company": "your-company",
    "street_address": "your-street-address",
    "city": "Bakersfield",
    "state": "CA",
    "zip_code": "93301",
    "notes": "your-notes",
    "tags": [
      "your-tags"
    ]
  }'
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/contacts/:id", {
  method: "PUT",
  headers: {
    "Authorization": "Bearer YOUR_JWT",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "first_name": "Jordan",
    "last_name": "Lee",
    "email": "agent@example.com",
    "phone": "+1 661 555 0123",
    "contact_type": "your-contact-type",
    "contact_status": "your-contact-status",
    "company": "your-company",
    "street_address": "your-street-address",
    "city": "Bakersfield",
    "state": "CA",
    "zip_code": "93301",
    "notes": "your-notes",
    "tags": [
      "your-tags"
    ]
  }),
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.put(
    "https://api.actuallycare.com/v1/contacts/:id",
    headers={"Authorization": "Bearer YOUR_JWT"},
    json={
        "first_name": "Jordan",
        "last_name": "Lee",
        "email": "agent@example.com",
        "phone": "+1 661 555 0123",
        "contact_type": "your-contact-type",
        "contact_status": "your-contact-status",
        "company": "your-company",
        "street_address": "your-street-address",
        "city": "Bakersfield",
        "state": "CA",
        "zip_code": "93301",
        "notes": "your-notes",
        "tags": [
            "your-tags"
        ]
    },
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Jordan",
    "last_name": "Lee",
    "full_name": "example full name",
    "email": "agent@example.com",
    "phone": "+1 661 555 0123",
    "contact_type": "buyer",
    "contact_status": "example contact status",
    "company": "example company",
    "license_number": "example license number",
    "street_address": "example street address",
    "city": "Bakersfield",
    "state": "CA",
    "zip_code": "93301",
    "notes": "example notes",
    "tags": [
      "example tags"
    ],
    "is_archived": true,
    "archived_at": "2026-07-15T14:32:10.000Z",
    "created_at": "2026-07-15T14:32:10.000Z",
    "updated_at": "2026-07-15T14:32:10.000Z"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Contact updated |
| `400` | Invalid request data |
| `401` | Authentication required |
| `404` | Resource not found |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Delete a contact

```http
DELETE /v1/contacts/{id}
```

Soft-deletes a contact. Deleted contacts disappear from all lists and searches and cannot be restored; to hide a contact reversibly, use archive instead.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes | Unique identifier (UUID) · Format: uuid |

### Example request

```bash title="cURL"
curl -X DELETE "https://api.actuallycare.com/v1/contacts/:id" \
  -H "Authorization: Bearer YOUR_JWT"
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/contacts/:id", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer YOUR_JWT",
  },
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.delete(
    "https://api.actuallycare.com/v1/contacts/:id",
    headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Jordan",
    "last_name": "Lee",
    "full_name": "example full name",
    "email": "agent@example.com",
    "phone": "+1 661 555 0123",
    "contact_type": "buyer",
    "contact_status": "example contact status",
    "company": "example company",
    "license_number": "example license number",
    "street_address": "example street address",
    "city": "Bakersfield",
    "state": "CA",
    "zip_code": "93301",
    "notes": "example notes",
    "tags": [
      "example tags"
    ],
    "is_archived": true,
    "archived_at": "2026-07-15T14:32:10.000Z",
    "created_at": "2026-07-15T14:32:10.000Z",
    "updated_at": "2026-07-15T14:32:10.000Z"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Contact deleted (response echoes the deleted contact record) |
| `401` | Authentication required |
| `403` | Caller lacks permission to delete this contact |
| `404` | Resource not found |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Archive a contact

```http
PATCH /v1/contacts/{id}/archive
```

Hides a contact from default lists. Archived contacts can be listed with `archived=true` and restored at any time.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes | Unique identifier (UUID) · Format: uuid |

### Example request

```bash title="cURL"
curl -X PATCH "https://api.actuallycare.com/v1/contacts/:id/archive" \
  -H "Authorization: Bearer YOUR_JWT"
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/contacts/:id/archive", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer YOUR_JWT",
  },
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.patch(
    "https://api.actuallycare.com/v1/contacts/:id/archive",
    headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Jordan",
    "last_name": "Lee",
    "full_name": "example full name",
    "email": "agent@example.com",
    "phone": "+1 661 555 0123",
    "contact_type": "buyer",
    "contact_status": "example contact status",
    "company": "example company",
    "license_number": "example license number",
    "street_address": "example street address",
    "city": "Bakersfield",
    "state": "CA",
    "zip_code": "93301",
    "notes": "example notes",
    "tags": [
      "example tags"
    ],
    "is_archived": true,
    "archived_at": "2026-07-15T14:32:10.000Z",
    "created_at": "2026-07-15T14:32:10.000Z",
    "updated_at": "2026-07-15T14:32:10.000Z"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Contact archived (response is the updated contact record) |
| `401` | Authentication required |
| `404` | Resource not found |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Restore an archived contact

```http
PATCH /v1/contacts/{id}/restore
```

Returns an archived contact to the active list.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes | Unique identifier (UUID) · Format: uuid |

### Example request

```bash title="cURL"
curl -X PATCH "https://api.actuallycare.com/v1/contacts/:id/restore" \
  -H "Authorization: Bearer YOUR_JWT"
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/contacts/:id/restore", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer YOUR_JWT",
  },
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.patch(
    "https://api.actuallycare.com/v1/contacts/:id/restore",
    headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Jordan",
    "last_name": "Lee",
    "full_name": "example full name",
    "email": "agent@example.com",
    "phone": "+1 661 555 0123",
    "contact_type": "buyer",
    "contact_status": "example contact status",
    "company": "example company",
    "license_number": "example license number",
    "street_address": "example street address",
    "city": "Bakersfield",
    "state": "CA",
    "zip_code": "93301",
    "notes": "example notes",
    "tags": [
      "example tags"
    ],
    "is_archived": true,
    "archived_at": "2026-07-15T14:32:10.000Z",
    "created_at": "2026-07-15T14:32:10.000Z",
    "updated_at": "2026-07-15T14:32:10.000Z"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Contact restored (response is the updated contact record) |
| `401` | Authentication required |
| `404` | Resource not found |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |
