Contacts API
REST endpoints for contacts — request and response reference with examples.
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 — see Errors.
List 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 |
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 |
Example request#
curl "https://api.actuallycare.com/v1/contacts" \
-H "Authorization: Bearer YOUR_JWT"const res = await fetch("https://api.actuallycare.com/v1/contacts", {
headers: {
"Authorization": "Bearer YOUR_JWT",
},
});
const data = await res.json();import requests
resp = requests.get(
"https://api.actuallycare.com/v1/contacts",
headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": {
"contacts": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "Jordan",
"last_name": "Lee",
"full_name": "example full name",
"email": "[email protected]",
"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
}
}
}Create a contact#
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 |
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 |
Example request#
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"
}'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();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)#
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "Jordan",
"last_name": "Lee",
"full_name": "example full name",
"email": "[email protected]",
"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"
}
}Search contacts#
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 |
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 |
Example request#
curl "https://api.actuallycare.com/v1/contacts/search" \
-H "Authorization: Bearer YOUR_JWT"const res = await fetch("https://api.actuallycare.com/v1/contacts/search", {
headers: {
"Authorization": "Bearer YOUR_JWT",
},
});
const data = await res.json();import requests
resp = requests.get(
"https://api.actuallycare.com/v1/contacts/search",
headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "Jordan",
"last_name": "Lee",
"full_name": "example full name",
"email": "[email protected]",
"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
}Get a contact#
Parameters#
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier (UUID) · Format: uuid |
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 |
Example request#
curl "https://api.actuallycare.com/v1/contacts/:id" \
-H "Authorization: Bearer YOUR_JWT"const res = await fetch("https://api.actuallycare.com/v1/contacts/:id", {
headers: {
"Authorization": "Bearer YOUR_JWT",
},
});
const data = await res.json();import requests
resp = requests.get(
"https://api.actuallycare.com/v1/contacts/:id",
headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "Jordan",
"last_name": "Lee",
"full_name": "example full name",
"email": "[email protected]",
"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"
}
}Update a contact#
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 |
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 |
Example request#
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": "[email protected]",
"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 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": "[email protected]",
"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();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": "[email protected]",
"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)#
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "Jordan",
"last_name": "Lee",
"full_name": "example full name",
"email": "[email protected]",
"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"
}
}Delete a contact#
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 |
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 |
Example request#
curl -X DELETE "https://api.actuallycare.com/v1/contacts/:id" \
-H "Authorization: Bearer YOUR_JWT"const res = await fetch("https://api.actuallycare.com/v1/contacts/:id", {
method: "DELETE",
headers: {
"Authorization": "Bearer YOUR_JWT",
},
});
const data = await res.json();import requests
resp = requests.delete(
"https://api.actuallycare.com/v1/contacts/:id",
headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "Jordan",
"last_name": "Lee",
"full_name": "example full name",
"email": "[email protected]",
"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"
}
}Archive a contact#
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 |
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 |
Example request#
curl -X PATCH "https://api.actuallycare.com/v1/contacts/:id/archive" \
-H "Authorization: Bearer YOUR_JWT"const res = await fetch("https://api.actuallycare.com/v1/contacts/:id/archive", {
method: "PATCH",
headers: {
"Authorization": "Bearer YOUR_JWT",
},
});
const data = await res.json();import requests
resp = requests.patch(
"https://api.actuallycare.com/v1/contacts/:id/archive",
headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "Jordan",
"last_name": "Lee",
"full_name": "example full name",
"email": "[email protected]",
"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"
}
}Restore an archived contact#
Returns an archived contact to the active list.
Parameters#
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier (UUID) · Format: uuid |
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 |
Example request#
curl -X PATCH "https://api.actuallycare.com/v1/contacts/:id/restore" \
-H "Authorization: Bearer YOUR_JWT"const res = await fetch("https://api.actuallycare.com/v1/contacts/:id/restore", {
method: "PATCH",
headers: {
"Authorization": "Bearer YOUR_JWT",
},
});
const data = await res.json();import requests
resp = requests.patch(
"https://api.actuallycare.com/v1/contacts/:id/restore",
headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "Jordan",
"last_name": "Lee",
"full_name": "example full name",
"email": "[email protected]",
"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"
}
}