API / Contacts
Contacts
People in the CRM: create or update by email, edit by id, read their timeline.
List contacts
GET/api/v1/contacts
Contacts in the workspace, newest first, in pages of up to 100. Filter by exact email, free text, tag, company, owner or lifecycle stage. Pass nextCursor back as cursor for the next page; it is null on the last page.
Query parameters
| Parameter | Type | Description |
|---|---|---|
email | string | Exact, case-insensitive email match. Returns at most one contact and ignores the other filters. |
q | string | Matches first name, last name, email or company name. |
tag | string | Only contacts carrying this tag. |
companyId | string | Only contacts at this company. |
ownerId | string | Only contacts owned by this user. |
lifecycleStage | string | Only contacts in this lifecycle stage, by value. |
limit | number | Page size, 1 to 100. Default 50. |
cursor | string | The nextCursor from the previous page. |
Example request
curl "https://crowdstack.org/api/v1/contacts?tag=outbound-q4&limit=1" \
-H "Authorization: Bearer $CROWDSTACK_API_KEY"Example response
{
"contacts": [
{
"id": "cmf3k2x9a0001abcd",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@acme.com",
"jobTitle": "Head of Growth",
"phone": null,
"website": "https://acme.com",
"linkedinUrl": "https://linkedin.com/in/janedoe",
"lifecycleStage": "lead",
"tags": [
"outbound-q4"
],
"fields": {
"demo_url": "https://example.com/demos/acme"
},
"owner": {
"id": "cmf3k2x9a0003abcd",
"name": "Jane Doe",
"email": "jane@acme-mail.com"
},
"company": {
"id": "cmf3k2x9a0002abcd",
"name": "Acme",
"domain": "acme.com"
},
"createdAt": "2026-09-15T10:12:00.000Z",
"updatedAt": "2026-09-15T10:12:00.000Z"
}
],
"nextCursor": "cmf3k2x9a0001abcd"
}Get a contact
GET/api/v1/contacts/:id
One contact by id, with counts of their tickets, deals, tasks and notes so you can decide what to fetch next.
Responses
| Status | Meaning |
|---|---|
| 404 | No contact with that id in this workspace. |
Example request
curl "https://crowdstack.org/api/v1/contacts/cmf3k2x9a0001abcd" \
-H "Authorization: Bearer $CROWDSTACK_API_KEY"Example response
{
"contact": {
"id": "cmf3k2x9a0001abcd",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@acme.com",
"jobTitle": "Head of Growth",
"phone": null,
"website": "https://acme.com",
"linkedinUrl": "https://linkedin.com/in/janedoe",
"lifecycleStage": "lead",
"tags": [
"outbound-q4"
],
"fields": {
"demo_url": "https://example.com/demos/acme"
},
"owner": {
"id": "cmf3k2x9a0003abcd",
"name": "Jane Doe",
"email": "jane@acme-mail.com"
},
"company": {
"id": "cmf3k2x9a0002abcd",
"name": "Acme",
"domain": "acme.com"
},
"createdAt": "2026-09-15T10:12:00.000Z",
"updatedAt": "2026-09-15T10:12:00.000Z",
"counts": {
"tickets": 2,
"deals": 1,
"tasks": 3,
"notes": 4
}
}
}Create or update a contact by email
POST/api/v1/contacts
Looks the contact up by email inside the workspace. Creates it if it does not exist, otherwise updates it. Keys you leave out are left alone, null clears a value, tags are added to the existing set, and custom fields merge key by key.
Custom field keys must exist under Settings, CRM, Fields. Unknown keys are rejected with 422 rather than dropped. Select fields accept either the option value or its label.
Request body
| Field | Type | Description |
|---|---|---|
emailrequired | string | Lower-cased and used as the match key. |
firstName | string | null | Defaults to the part of the email before @ for a new contact with no name. |
lastName | string | null | — |
jobTitle | string | null | — |
phone | string | null | Stored as given; no formatting is applied. |
website | string | null | Normalised to a URL. |
linkedinUrl | string | null | Normalised to a linkedin.com profile URL. |
lifecycleStage | string | null | One of the workspace's lifecycle stages, by value or label. |
tags | string[] | Added to the contact's tags. Never removes existing tags. |
fields | object | Custom field values keyed by field key. Unknown keys are rejected; get_workspace lists the defined fields. |
company | object | null | Matched by website domain, then case-insensitive name; created when neither matches. null unlinks the contact from its company. |
Responses
| Status | Meaning |
|---|---|
| 201 | A new contact was created. Body: { contact, created: true }. |
| 200 | An existing contact was updated. Body: { contact, created: false }. |
Example request
curl -X POST https://crowdstack.org/api/v1/contacts \
-H "Authorization: Bearer $CROWDSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@acme.com",
"firstName": "Jane",
"lastName": "Doe",
"jobTitle": "Head of Growth",
"lifecycleStage": "lead",
"tags": [
"outbound-q4"
],
"company": {
"name": "Acme",
"website": "https://acme.com"
}
}'Example response
{
"created": true,
"contact": {
"id": "cmf3k2x9a0001abcd",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@acme.com",
"jobTitle": "Head of Growth",
"phone": null,
"website": "https://acme.com",
"linkedinUrl": "https://linkedin.com/in/janedoe",
"lifecycleStage": "lead",
"tags": [
"outbound-q4"
],
"fields": {
"demo_url": "https://example.com/demos/acme"
},
"owner": {
"id": "cmf3k2x9a0003abcd",
"name": "Jane Doe",
"email": "jane@acme-mail.com"
},
"company": {
"id": "cmf3k2x9a0002abcd",
"name": "Acme",
"domain": "acme.com"
},
"createdAt": "2026-09-15T10:12:00.000Z",
"updatedAt": "2026-09-15T10:12:00.000Z"
}
}Update a contact
PATCH/api/v1/contacts/:id
Change any of a contact's fields by id. Only keys you send are changed; null clears a value. Use tags to replace the set, or addTags and removeTags to adjust it.
Request body
| Field | Type | Description |
|---|---|---|
email | string | null | Change the primary email. Must not belong to another contact. null removes it. |
firstName | string | — |
lastName | string | null | — |
jobTitle | string | null | — |
phone | string | null | — |
website | string | null | — |
linkedinUrl | string | null | — |
lifecycleStage | string | null | One of the workspace's lifecycle stages, by value or label. null clears it. |
tags | string[] | Replaces the whole tag set. |
addTags | string[] | Tags to add to the existing set. |
removeTags | string[] | Tags to remove from the existing set. |
fields | object | Custom field values keyed by field key. Unknown keys are rejected; get_workspace lists the defined fields. |
companyId | string | null | Link the contact to this company. null unlinks. |
ownerId | string | null | The member who owns this contact. null clears the owner. |
Responses
| Status | Meaning |
|---|---|
| 200 | Body: { contact }. |
| 404 | No contact with that id. |
| 422 | A value did not validate, or the email belongs to another contact. |
Example request
curl -X PATCH https://crowdstack.org/api/v1/contacts/cmf3k2x9a0001abcd \
-H "Authorization: Bearer $CROWDSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"lifecycleStage": "customer",
"addTags": [
"won"
],
"ownerId": "cmf3k2x9a0003abcd"
}'Example response
{
"contact": {
"id": "cmf3k2x9a0001abcd",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@acme.com",
"jobTitle": "Head of Growth",
"phone": null,
"website": "https://acme.com",
"linkedinUrl": "https://linkedin.com/in/janedoe",
"lifecycleStage": "customer",
"tags": [
"outbound-q4",
"won"
],
"fields": {
"demo_url": "https://example.com/demos/acme"
},
"owner": {
"id": "cmf3k2x9a0003abcd",
"name": "Jane Doe",
"email": "jane@acme-mail.com"
},
"company": {
"id": "cmf3k2x9a0002abcd",
"name": "Acme",
"domain": "acme.com"
},
"createdAt": "2026-09-15T10:12:00.000Z",
"updatedAt": "2026-09-15T10:12:00.000Z"
}
}Delete a contact
DELETE/api/v1/contacts/:id
Permanently deletes a contact along with their notes, activity and campaign memberships. Refused while the contact still has tickets; reassign or delete those first.
Responses
| Status | Meaning |
|---|---|
| 200 | Body: { deleted: true }. |
| 404 | No contact with that id. |
| 422 | The contact still has tickets. |
Example request
curl -X DELETE https://crowdstack.org/api/v1/contacts/cmf3k2x9a0001abcd \
-H "Authorization: Bearer $CROWDSTACK_API_KEY"Example response
{
"deleted": true
}List a contact's activity
GET/api/v1/contacts/:id/activity
The contact's timeline, newest first: tickets opened, replies, campaign sends and replies, deal stage moves, tasks, subscription changes. Each entry names the product it came from and carries a short title, optional body and metadata such as the record id.
Query parameters
| Parameter | Type | Description |
|---|---|---|
product | support | crm | tasks | revenue | campaigns | Only entries from this product. |
limit | number | Page size, 1 to 100. Default 50. |
cursor | string | The nextCursor from the previous page. |
Example request
curl "https://crowdstack.org/api/v1/contacts/cmf3k2x9a0001abcd/activity" \
-H "Authorization: Bearer $CROWDSTACK_API_KEY"Example response
{
"activity": [
{
"id": "cmf3k2x9a0070abcd",
"product": "crm",
"type": "deal.stage_changed",
"title": "Deal moved to Proposal",
"body": "Acme renewal: Qualified → Proposal",
"meta": {
"dealId": "cmf3k2x9a0080abcd"
},
"actor": {
"id": "cmf3k2x9a0003abcd",
"name": "Jane Doe",
"email": "jane@acme-mail.com"
},
"occurredAt": "2026-09-15T10:12:00.000Z"
}
],
"nextCursor": null
}