Contacts and roles

A contact is someone the user deals with by email, a person or a business. A role is what that contact is to the user: a client, a vendor, their accountant. One contact can hold several roles. This guide covers how bind.co builds both from the mailbox, how duplicates are handled, how a person stays in control, and which webhook tells you something changed.

Reading needs contacts:read; changing needs contacts:write, which includes contacts:read.

A contact

{
  "contact_id": "con_3SrGoE2zltj",
  "contact_status": "active",
  "contact_name": {"value": "Dana Ruiz", "bind_inference_confidence": 0.95},
  "contact_primary_email": {"value": "dana@acme.com", "bind_inference_confidence": 0.99},
  "contact_title": {"value": "Controller", "bind_inference_confidence": 0.9},
  "contact_summary": {"value": "Acme's controller; handles the monthly close.", "bind_inference_confidence": 0.84},
  "contact_relationship_strength": {"value": 7, "bind_inference_confidence": 0.7},
  "contact_tone": {"value": 4, "bind_inference_confidence": 0.66},
  "first_contact": "2024-02-12T15:03:00Z",
  "last_contact": "2026-10-02T18:20:00Z",
  "user_direct_first_contact": "2024-02-12T15:03:00Z",
  "user_direct_last_contact": "2026-09-30T11:20:00Z",
  "contact_direct_first_contact": "2024-02-13T08:41:00Z",
  "contact_direct_last_contact": "2026-10-02T18:20:00Z",
  "user_indirect_first_contact": null,
  "user_indirect_last_contact": null,
  "other_indirect_first_contact": null,
  "other_indirect_last_contact": null,
  "truth_updated_at": null,
  "suggestion_updated_at": null
}

Every learned detail has a bind_inference_confidence; a detail a person set has 1. How to use the score: Reading confidence.

bind.co sets newsletters and mailing lists aside on its own: they become dismissed and leave the lists, and come back if the user starts corresponding with them.

To add someone the mailbox has not shown yet, POST /v1/contacts with a name and email address. The contact is saved as set by a person. An address another contact already holds, or a user whose mailbox has not been read yet, answers 409.

Roles

{
  "category_contact_role_id": "rol_1S9dCsGYrSI",
  "contact_id": "con_3SrGoE2zltj",
  "contact_role_status": "active",
  "entity_type": {"value": "ACCOUNTANT", "bind_inference_confidence": 0.92},
  "entity_stage": {"value": "ACTIVE", "bind_inference_confidence": 0.81},
  "home_currency": {"value": "USD", "bind_inference_confidence": 0.77},
  "entity_type_inferred": {"value": null, "bind_inference_confidence": null},
  "entity_type_inferred_streak": 0
}

A contact holds each type at most once. An accountant who is also a friend has two roles, each with its own id, stage and history. GET /v1/roles lists roles with their contact's name, email and contact dates, so a "my clients" screen is one call: GET /v1/roles?entity_type=CLIENT&entity_stage=ACTIVE.

Roles are kept up to date from the mail. When the mail points to a different type than the one a role has, entity_type_inferred shows it and entity_type_inferred_streak how many updates in a row have pointed to it. GET /v1/roles/families shows how types group into families, such as accountants under vendors.

Drafts are written per role; see Drafts and goals. Operations name roles too, for example the counterpart of an expense; see Operations.

Who has the last word

Contacts and roles follow the same steps as the profile:

  1. Nobody has saved it: bind.co keeps it up to date from the mail.
  2. A person saves it with PATCH (the fields sent, with confidence 1) or confirm (as it is). New mail no longer changes the saved values.
  3. When the mail later reads differently, bind.co flags a suggestion: suggestion_updated_at is set and your endpoint receives contacts.suggestion_available or contact_roles.suggestion_available.
  4. GET /v1/contacts/{contact_id}/suggestions or GET /v1/roles/{role_id}/suggestions shows what differs, current and suggested (example below). PATCH the suggested values to accept, or confirm to keep what is saved.
  5. reset hands it back to bind.co.

A role suggestion:

{
  "suggestion_updated_at": "2026-10-03T07:41:12Z",
  "entity_stage": {
    "current": {"value": "PROSPECT", "bind_inference_confidence": 1.0},
    "suggested": {"value": "ACTIVE", "bind_inference_confidence": 0.84}
  }
}

To find everything waiting for review: GET /v1/contacts?has_suggestion=true and GET /v1/roles?has_suggestion=true.

Set aside and bring back

One person, one contact

bind.co checks for duplicates and merges them on its own. A person can merge too:

curl https://api.bind.co/v1/contacts/con_3SrGoE2zltj/merge \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"loser_contact_ids": ["con_8LmQe4XyTb1"]}'

The addresses, roles and history of each listed contact move to this one, and they become merged. Every listed contact must be active and the user's; if one is not, nothing is merged.

POST /v1/contacts/{contact_id}/unmerge undoes merges into this contact, whoever made them: each contact comes back active with its own id, addresses, roles and history. Leave the list out to undo them all. Once two contacts are separated, bind.co never merges them again on its own. A contact changed since the merge, or a later merge touching them, answers 409 and nothing is undone.

If your application stores contact ids, handle merged: when a stored contact answers 404, re-read the contact list to find where it went.

Webhooks

The webhooks guide has every payload.