Prospect contacts
Create, match, read, update and delete prospect contacts through the /prospects endpoints.
A prospect contact is a person you're prospecting who isn't yet a Contact at one of your Accounts. In the API they live at /prospects: every /prospects endpoint reads or writes prospect contacts. A prospect contact can be linked to a Prospect Company.
POST /prospects is built for capturing prospect contacts from forms and other tools: it matches on email, so sending the same person twice updates them instead of creating a duplicate.
The prospect contact object#
| Field | Type | Notes |
|---|---|---|
id |
string | Opaque string ID. |
first_name |
string | Can be empty. |
last_name |
string | Can be empty. |
email |
string or null | Stored in lowercase. |
phone |
string or null | |
job_title |
string or null | |
linkedin_url |
string or null | |
stage_id |
string | Prospect stage. See Stage IDs. |
owner_id |
string or null | User ID of the person in Assigned To. |
lead_company_id |
string or null | The linked Prospect Company. |
converted_at |
string or null | When the prospect contact was converted. Read-only. |
archived_at |
string or null | When it was archived. Read-only. |
created_at |
string | ISO 8601 timestamp. |
updated_at |
string | ISO 8601 timestamp. |
custom_fields |
object | Your custom fields for prospect contacts, keyed by internal name. See Custom fields. |
source_id (where the prospect contact came from) can be set on create and update but isn't returned in responses.
{
"id": "e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63",
"first_name": "Sam",
"last_name": "Lee",
"email": "sam.lee@fabrikam.example",
"phone": "+1 646 555 0123",
"job_title": "Operations Director",
"linkedin_url": null,
"stage_id": "rps_new",
"owner_id": "5b8d1f3a-7e2c-4a9b-b6d4-1c3e5f7a9b20",
"lead_company_id": "cm3k9x2p10004qz8f7h2d1a6b",
"converted_at": null,
"archived_at": null,
"created_at": "2026-11-12T09:30:00.000Z",
"updated_at": "2026-11-12T09:30:00.000Z",
"custom_fields": {
"budget_band": "50k-100k",
"wants_demo": true
}
}
Stage IDs#
stage_id |
Stage |
|---|---|
rps_new |
New (the default for new prospect contacts) |
rps_contacted |
Contacted |
rps_qualified |
Qualified |
rps_converted |
Converted |
rps_disqualified |
Disqualified |
List prospect contacts#
GET /prospects · Scope: prospects:read
Returns prospect contacts newest first, with their custom fields. Archived prospect contacts aren't listed.
Query parameters#
| Parameter | Type | Required | Notes |
|---|---|---|---|
limit |
integer | No | 1 to 100. Default 20. |
cursor |
string | No | meta.cursor from the previous page. |
Example#
curl "https://crm.empiraa.app/api/v1/prospects?limit=100" \
-H "Authorization: Bearer YOUR_API_KEY"
const res = await fetch("https://crm.empiraa.app/api/v1/prospects?limit=100", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const { data, meta } = await res.json();
Response 200#
An array of prospect contact objects in data. meta has cursor, hasMore and total (the number of prospect contacts in the workspace).
{
"data": [ { "id": "e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63", "first_name": "Sam", "last_name": "Lee", "email": "sam.lee@fabrikam.example", "stage_id": "rps_new", "custom_fields": {} } ],
"meta": { "cursor": null, "total": 1, "hasMore": false },
"error": null
}
(Shortened: each item is a full prospect contact object.)
Create or update a prospect contact#
POST /prospects · Scope: prospects:write · Supports Idempotency-Key
If a prospect contact with the same email already exists in the workspace, it's updated with the fields you send and the response is 200 with "_action": "updated". Otherwise a new one is created and the response is 201 with "_action": "created".
Request body#
| Field | Type | Required | Notes |
|---|---|---|---|
email |
string or null | No | Used to find an existing prospect contact. Trimmed and lowercased. |
first_name |
string | No | |
last_name |
string | No | |
username |
string | No | Used as first_name when neither first_name nor last_name is sent. Handy for form tools that only send one name field. |
phone |
string or null | No | |
job_title |
string or null | No | |
linkedin_url |
string or null | No | |
stage_id |
string | No | See Stage IDs. New prospect contacts default to rps_new. |
source_id |
string or null | No | An active prospect source. |
owner_id |
string or null | No | Assigned To: an active user in the workspace. |
lead_company_id |
string or null | No | A Prospect Company in the workspace. null unlinks. |
notes |
string | No | Added as a new note on the prospect contact. Blank values are ignored, and existing notes are never removed. |
custom_fields |
object | No | Custom field values by internal name. |
| any other field | any | No | Treated as a custom field when its name matches a custom field's internal name. |
All fields are optional, but send at least an email or a name.
Email from other fields. If email is missing or empty, the first other field whose name contains "email" and whose value contains "@" is used as the email. This lets form tools that name the field differently (such as work_email) still match.
When a prospect contact is matched, only the fields you send are changed. An empty first_name or last_name doesn't overwrite an existing name.
Example#
curl -X POST https://crm.empiraa.app/api/v1/prospects \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 2c7e4a90-6b1d-4f3e-8a5c-0d9b7e3f1a26" \
-d '{
"email": "sam.lee@fabrikam.example",
"first_name": "Sam",
"last_name": "Lee",
"job_title": "Operations Director",
"source_id": "rls_website",
"notes": "Requested a demo from the pricing page.",
"custom_fields": { "budget_band": "50k-100k" }
}'
const res = await fetch("https://crm.empiraa.app/api/v1/prospects", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
email: "sam.lee@fabrikam.example",
first_name: "Sam",
last_name: "Lee",
job_title: "Operations Director",
source_id: "rls_website",
notes: "Requested a demo from the pricing page.",
custom_fields: { budget_band: "50k-100k" },
}),
});
const { data } = await res.json();
console.log(data._action); // "created" or "updated"
Response 201 or 200#
{
"data": {
"id": "e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63",
"first_name": "Sam",
"last_name": "Lee",
"email": "sam.lee@fabrikam.example",
"phone": null,
"job_title": "Operations Director",
"linkedin_url": null,
"stage_id": "rps_new",
"owner_id": null,
"lead_company_id": null,
"converted_at": null,
"archived_at": null,
"created_at": "2026-11-12T09:30:00.000Z",
"updated_at": "2026-11-12T09:30:00.000Z",
"custom_fields": { "budget_band": "50k-100k", "wants_demo": null },
"_action": "created"
},
"meta": { "cursor": null },
"error": null
}
Errors#
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST |
owner_id, source_id or lead_company_id isn't a string or null. |
| 404 | NOT_FOUND |
owner_id isn't an active user in the workspace ("User not found"), lead_company_id isn't a Prospect Company in the workspace, or source_id isn't an active prospect source. |
Webhook events: prospect.created for a new prospect contact, prospect.updated for a matched one. Also starts any Autopilot workflows that run when a prospect contact is created, updated or changes stage.
Get a prospect contact#
GET /prospects/{id} · Scope: prospects:read
Path parameters#
| Parameter | Type | Notes |
|---|---|---|
id |
string | Prospect contact ID. |
Example#
curl https://crm.empiraa.app/api/v1/prospects/e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63 \
-H "Authorization: Bearer YOUR_API_KEY"
await fetch("https://crm.empiraa.app/api/v1/prospects/e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
Response 200#
The prospect contact object in data.
Errors#
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND |
No prospect contact with that ID in this workspace. |
Update a prospect contact#
PATCH /prospects/{id} · Scope: prospects:write
Send only the fields you want to change.
Request body#
| Field | Type | Notes |
|---|---|---|
first_name |
string | |
last_name |
string | |
email |
string or null | |
phone |
string or null | |
job_title |
string or null | |
linkedin_url |
string or null | |
stage_id |
string | See Stage IDs. |
source_id |
string or null | An active prospect source, or null to clear. |
owner_id |
string or null | Assigned To: an active user in the workspace, or null for nobody. |
lead_company_id |
string or null | A Prospect Company in the workspace, or null to unlink. |
notes |
string | Added as a new note. Blank values are ignored. |
custom_fields |
object | Custom field values by internal name. Other unrecognized top-level fields are also treated as custom fields. |
Example#
curl -X PATCH https://crm.empiraa.app/api/v1/prospects/e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"stage_id": "rps_qualified", "owner_id": "5b8d1f3a-7e2c-4a9b-b6d4-1c3e5f7a9b20"}'
await fetch("https://crm.empiraa.app/api/v1/prospects/e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63", {
method: "PATCH",
headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ stage_id: "rps_qualified", owner_id: "5b8d1f3a-7e2c-4a9b-b6d4-1c3e5f7a9b20" }),
});
Response 200#
The updated prospect contact object in data.
Errors#
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST |
owner_id, source_id or lead_company_id isn't a string or null. |
| 404 | NOT_FOUND |
The prospect contact, user, Prospect Company or prospect source doesn't exist in this workspace. |
Webhook events: prospect.updated.
Delete a prospect contact#
DELETE /prospects/{id} · Scope: prospects:write
Moves the prospect contact to Recently deleted in Signal. It stays there for 30 days, during which it can be restored from Settings → Recently deleted. After 30 days it's removed for good.
Example#
curl -X DELETE https://crm.empiraa.app/api/v1/prospects/e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63 \
-H "Authorization: Bearer YOUR_API_KEY"
await fetch("https://crm.empiraa.app/api/v1/prospects/e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63", {
method: "DELETE",
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
Response 204#
No body.
Errors#
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND |
No prospect contact with that ID in this workspace. |
Webhook events: prospect.deleted.

