GoodInbound

Contacts and Tags

All routes here need a project API key: Authorization: Bearer gi_live_.... See Authentication.

Contacts#

Manage CRM records, custom properties, saved views, activity, journeys, next actions, stages, imports, and enrichment. Contacts are also created automatically when a form is submitted or a booking is made.

Method Path Description
GET /api/projects/:projectId/contacts List, filter, sort, and group contacts.
POST /api/projects/:projectId/contacts Create a contact.
POST /api/projects/:projectId/contacts/import Import mapped contact rows.
GET /api/projects/:projectId/contacts/facets Lead source and company values with counts under the current filters.
POST /api/projects/:projectId/contacts/bulk Add or remove a tag, set a property or lead source, or delete up to 500 contacts.
GET /api/projects/:projectId/contact-properties List custom contact properties.
POST /api/projects/:projectId/contact-properties Create a contact property.
PATCH /api/projects/:projectId/contact-properties/:key Rename, reorder, or change a property's options.
DELETE /api/projects/:projectId/contact-properties/:key Delete a property and its value on every contact.
GET /api/projects/:projectId/contacts/:contactId Get a contact with tags.
PUT /api/projects/:projectId/contacts/:contactId Update a contact.
DELETE /api/projects/:projectId/contacts/:contactId Delete a contact.
GET /api/projects/:projectId/contacts/:contactId/activities List the cursor-paginated contact timeline and category counts.
GET /api/projects/:projectId/contacts/:contactId/journeys Where the contact's bookings and form responses came from: landing page, referrer, UTMs, ad click IDs, and steps (about 3 months kept).
GET /api/projects/:projectId/journey The journey of one booking or form response (bookingId or formResponseId).
PUT /api/projects/:projectId/contacts/:contactId/next-action Set, replace, or complete the contact's next action.
POST /api/projects/:projectId/contacts/:contactId/stage Move a contact between stage tags.
POST /api/projects/:projectId/contacts/:contactId/enrich Run contact research and return the enriched contact.
GET /api/projects/:projectId/contact-views List saved contact views.
POST /api/projects/:projectId/contact-views Create a saved contact view.
PUT /api/projects/:projectId/contact-views/:viewId Update a saved contact view.
DELETE /api/projects/:projectId/contact-views/:viewId Delete a saved contact view.
POST /api/projects/:projectId/pipeline/seed Create the default pipeline stages when missing.
  • Lead source. Every contact has a leadSource: what created it ("Booking: Intro call", "Form: Contact us", "Manual", "API", "MCP", "Import"), or a custom value you send. leadSourceType and leadSourceId identify the record.
  • Fields. Contact writes support name, email, phone, notes, leadSource, properties, company, companyWebsite, position, companySize, estimatedRevenue, and linkedinUrl.
  • Custom properties. properties is an object keyed by property key. Updates merge it, and null clears a key. Property PATCH with options adds or updates the listed options and keeps the rest; send removeOptions with option values to delete them and clear them from every contact.
  • Next action. Set it with { text, deadline }; send both as null to complete it.

Create a contact#

POST /api/projects/:projectId/contacts

Property Type Required Description
name string Yes Contact's full name
email string No Contact's email address
phone string No Contact's phone number
notes string No Internal notes about the contact
leadSource string No Where the lead came from, e.g. "Partner referral". Defaults to "API".
properties object No Custom property values keyed by property key
curl -X POST "https://goodinbound.com/api/projects/proj_123/contacts" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer gi_live_your_api_key" \
  -d '{
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+1-555-0123",
    "notes": "Met at conference",
    "leadSource": "Partner referral"
  }'
{
  "contact": {
    "id": "ct_p4q5r6",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+1-555-0123",
    "notes": "Met at conference",
    "leadSource": "Partner referral",
    "createdAt": "2026-03-24T14:00:00Z"
  }
}

Update a contact#

PUT /api/projects/:projectId/contacts/:contactId

Only the fields you send change. properties is merged into the existing values; send null for a key to clear it.

curl -X PUT "https://goodinbound.com/api/projects/proj_123/contacts/ct_p4q5r6" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer gi_live_your_api_key" \
  -d '{
    "name": "Jane Smith-Doe",
    "notes": "Updated: now a paying customer"
  }'

List, filter, and group contacts#

GET /api/projects/:projectId/contacts returns { contacts, total }, 50 at a time by default. Use limit and offset to load more.

Parameter Type Description
search string Match name, email, phone, or company
tagIds string[] Repeat the key to filter by multiple tags (tagId for one)
matchAllTags boolean Require every tagIds value instead of any
stageTagId string Require the current stage tag
excludeStageTagIds string[] Repeated stage IDs to exclude
activityType enum Filter by the latest supported activity type
activitySinceDays number Require activity within this many days
noActivitySinceDays number Require no activity within this many days
bookingStatus enum Filter by related booking status
leadSource string[] Repeat for each lead source label (up to 40)
companyDomain string[] Repeat for each company domain (up to 40); __none__ matches contacts without one
createdFrom, createdTo string Created date range, YYYY-MM-DD
rules JSON Array of { field, op, value }. field is a built-in field or property:<key>; op is is, is_not, contains, is_set, is_empty, gt, lt, or any_of.
sort, dir string name, email, company, leadSource, createdAt, lastActivityAt, nextActionDeadline, or property:<key>; asc or desc
groupBy string company, leadSource, stage, or property:<key>. Returns { groups: [{ key, label, count, contacts }], total, hiddenGroups } with the 100 largest groups.
group string With groupBy, page through one group
limit integer Page size; default 50, maximum 100
offset integer Zero-based result offset
curl --get "https://goodinbound.com/api/projects/proj_123/contacts" \
  -H "Authorization: Bearer gi_live_your_api_key" \
  --data-urlencode "tagIds=tag_lead" \
  --data-urlencode "tagIds=tag_vip" \
  --data-urlencode "matchAllTags=true" \
  --data-urlencode "limit=50"
{
  "contacts": [
    {
      "id": "ct_m1n2o3",
      "name": "Jane Smith",
      "email": "jane@example.com",
      "phone": "+1-555-0123",
      "tags": [{ "id": "tag_lead", "name": "Lead", "color": "#1B4332" }],
      "lastActivityAt": "2026-07-22T08:30:00.000Z",
      "createdAt": "2026-03-20T10:00:00.000Z"
    }
  ],
  "total": 1
}

Contact activity#

Activity accepts category values all, bookings, form_responses, or workflows. The default page size is 20 and the maximum is 100. Pass the returned opaque nextCursor unchanged to load the next page.

curl --get "https://goodinbound.com/api/projects/proj_123/contacts/ct_123/activities" \
  -H "Authorization: Bearer gi_live_your_api_key" \
  --data-urlencode "category=bookings" \
  --data-urlencode "limit=20"

Tags#

Create project tags and assign them to contacts. Names are unique within a project after trimming and case normalization.

Method Path Description
GET /api/projects/:projectId/tags List tags with search and cursor pagination.
POST /api/projects/:projectId/tags Create a tag from a name and optional #RRGGBB color.
GET /api/projects/:projectId/tags/:tagId Get a tag.
PATCH /api/projects/:projectId/tags/:tagId Update a tag's name or color.
DELETE /api/projects/:projectId/tags/:tagId Delete an unused tag.
PUT /api/projects/:projectId/contacts/:contactId/tags/:tagId Assign a tag to a contact.
DELETE /api/projects/:projectId/contacts/:contactId/tags/:tagId Remove a tag from a contact.
POST /api/projects/:projectId/contacts/:contactId/tags Older assignment endpoint taking { tagId }; use PUT instead.
Parameter Type Description
search string Case-insensitive name search, 1–100 characters
limit integer Optional page size from 1 to 100
cursor string Opaque nextCursor value; requires limit
name string Required on create. Project-unique name, 1–50 characters
color string Optional six-digit hex color such as #1B4332
curl -X POST "https://goodinbound.com/api/projects/proj_123/tags" \
  -H "Authorization: Bearer gi_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name":"Qualified lead","color":"#1B4332"}'
{
  "tags": [
    {
      "id": "tag_123",
      "projectId": "proj_123",
      "name": "Qualified lead",
      "color": "#1B4332",
      "createdAt": "2026-07-22T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}

Assigning and removing are idempotent. Assignment returns { tag, assigned } and removal returns { success, tag, removed }; repeating either succeeds and reports false when nothing changed.

curl -X PUT \
  "https://goodinbound.com/api/projects/proj_123/contacts/ct_123/tags/tag_123" \
  -H "Authorization: Bearer gi_live_your_api_key"

Deleting a tag a workflow uses returns 409:

{
  "error": "Tag is referenced by one or more workflows",
  "code": "TAG_IN_USE",
  "workflows": [{ "id": "wf_123", "name": "Research new leads" }]
}