List contacts

GET/api/v1/contacts

Contacts in the workspace, newest first, in pages of up to 100. Filter by exact email, a free-text search, or a tag. Pass nextCursor back as cursor to fetch the following page; it is null on the last page.

Query parameters

ParameterTypeDescription
emailstringExact, case-insensitive match. Returns at most one contact and ignores the other filters.
qstringMatches first name, last name, email or company name.
tagstringOnly contacts carrying this tag.
limitnumber1 to 100. Default 50.
cursorstringThe nextCursor value from the previous page.

Responses

StatusMeaning
200Body: { contacts: [...], nextCursor: string | null }.

Example request

curl "https://crowdstack.org/api/v1/contacts?tag=outbound-q4&limit=2" \
  -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", "plan_interest": "Growth" },
      "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, in the same shape the list and upsert endpoints return.

Responses

StatusMeaning
200Body: { contact }.
404No 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", "plan_interest": "Growth" },
    "company": { "id": "cmf3k2x9a0002abcd", "name": "Acme", "domain": "acme.com" },
    "createdAt": "2026-09-15T10:12:00.000Z",
    "updatedAt": "2026-09-15T10:12:00.000Z"
  }
}

Create or update a contact

POST/api/v1/contacts

Looks the contact up by email inside your 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, so a typo never reaches a live campaign as a blank merge field. Select fields accept either the option value or its label.

Company matching tries the website's domain first, then a case-insensitive name match, and creates the company when neither matches. Pass null to unlink the contact from its company.

Request body

FieldTypeDescription
emailrequiredstringLower-cased and used as the match key.
firstNamestring | nullDefaults to the part of the email before @ when a new contact has no name.
lastNamestring | null
jobTitlestring | null
phonestring | nullStored as given; no formatting is applied.
websitestring | nullNormalised to a URL.
linkedinUrlstring | nullNormalised to a linkedin.com profile URL.
lifecycleStagestring | nullOne of your workspace's lifecycle stages, by value or label.
tagsstring[]Added to the contact's tags. Never removes existing tags. Up to 50.
fieldsobjectCustom field values keyed by field key. Strings, numbers, booleans or null, matching each field's type.
companyobject | null{ name?, website? }. Matched or created as described above.

Responses

StatusMeaning
201A new contact was created. Body: { contact, created: true }.
200An existing contact was updated. Body: { contact, created: false }.
422Validation failed; see details for the offending paths.

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" },
    "fields": { "demo_url": "https://example.com/demos/acme" }
  }'

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", "plan_interest": "Growth" },
    "company": { "id": "cmf3k2x9a0002abcd", "name": "Acme", "domain": "acme.com" },
    "createdAt": "2026-09-15T10:12:00.000Z",
    "updatedAt": "2026-09-15T10:12:00.000Z"
  }
}