---
title: "Contacts API Reference"
description: "Search, read and update Chatfuel contacts: attributes, names, notes, assignees and sales stages, segments, CSV import and export, and contact events."
canonical_url: https://chatfuel.com/docs/public-api/contacts
markdown_url: https://chatfuel.com/docs/public-api/contacts.md
last_updated: 2026-10-06
lang: en
site: https://chatfuel.com/docs
llms_txt: https://chatfuel.com/llms.txt
---

# Contacts API Reference

Search, read and update Chatfuel contacts: attributes, names, notes, assignees and sales stages, segments, CSV import and export, and contact events.

A contact is a person who has talked to the bot on WhatsApp, Instagram, Facebook, TikTok or the website widget. Through the Public API you can search contacts with segments, read their attributes, change names, attributes and notes, assign them to teammates or Fuely AI, move them between sales stages, and import or export them as CSV. Reading contacts works for **Agent** virtual users; editing a contact's data needs **Editor**.

Contacts are reached through `bot(id:)`. A contact's ID is also the ID of its conversation, as described in [messaging](https://chatfuel.com/docs/public-api/messaging).

## How to search and read contacts [#how-to-search-and-read-contacts]

```graphql
type Bot {
  # Agent. One contact by ID.
  contact(id: ContactID!): Contact!
  # Agent. Paginated search, up to 500 per page, optionally filtered by a segment and sorted by an attribute.
  contactsConnection(
    platforms: [Platform!]!          # whatsapp, instagram, facebook, tiktok, widget
    first: Int!
    before: ContactSearchCursor
    after: ContactSearchCursor
    segment: SegmentInput
    orderBy: ContactSearchOrderByInput   # { orderBy: <attribute name>, direction: Asc | Desc }
  ): ContactConnection!
  # Agent. Number of contacts matching the platforms and segment.
  contactsCount(platforms: [Platform!]!, segment: SegmentInput): Int!
  contactsTotalCount(platforms: [Platform!]!, segment: SegmentInput): Int!
}
```

`Contact` is an interface; WhatsApp contacts are `WhatsappContact` with an extra `phone` field, and there are similar types for the other channels. Every contact has:

```graphql
query Contact($botID: BotID!, $contactID: ContactID!) {
  bot(id: $botID) {
    contact(id: $contactID) {
      id
      name
      note
      updatedAt
      salesStageV2              # New, Sorting, Ready, WorkingOn, Won, Lost
      lastConversationMessageTime
      unreadMessagesCount
      unhandledSwitchToHuman    # handed to a human and not opened yet
      assignee {
        ... on PublicUserAccount { id name accountType }
        ... on FuelyAIAssignee { __typename }
      }
      attributes(names: ["email", "city"]) {
        attr { name type }
        value {
          ... on BotAttributeValueString { stringValue }
          ... on BotAttributeValueDouble { doubleValue }
          ... on BotAttributeValueBoolean { booleanValue }
          ... on BotAttributeValueDatetime { datetimeValue }
        }
      }
      ... on WhatsappContact { phone }
    }
  }
}
```

## How to filter contacts with segments [#how-to-filter-contacts-with-segments]

A segment is a set of filters joined with `AND` or `OR`. Segment and filter IDs are UUIDs that you generate, or call `segmentNew` to get an empty segment with an ID:

```graphql
query Leads($botID: BotID!) {
  bot(id: $botID) {
    contactsConnection(
      platforms: [whatsapp]
      first: 100
      segment: {
        id: "2b0c6a0e-6f0a-4a39-9d63-0b1f0d4f7a10"
        resultOperator: AND
        filters: [{
          id: "8f6d2a4e-1c3b-4f5a-9e7d-2a1b3c4d5e6f"
          byAttribute: { name: "city", defaultStrategy: { operator: IS, comparableValues: ["Lisbon"] } }
        }]
      }
    ) {
      edges { node { id name } }
      pageInfo { hasNextPage endCursor }
    }
  }
}
```

Attribute filters support the operators `IS`, `IS_NOT`, `STARTS_WITH`, `CONTAINS`, `LT`, `GT`, `IS_EMPTY` and `IS_NOT_EMPTY`; date attributes use `dateStrategy`. `bot.botAttributes(...)` lists the system and custom attributes a bot has, with their data types (**Agent**).

## How to update a contact [#how-to-update-a-contact]

```graphql
type Mutation {
  # Editor. Errors: ContactDoesNotExist, ContactNameTooLong, ContactNameRequired
  contactUpdateName(id: ContactID!, name: String!): Contact!
  # Editor. Creates or updates a custom attribute. System attributes can't be changed (SystemAttributeUpdateNotAllowed).
  contactAttributeUpdate(id: ContactID!, attrName: AttributeName!, attrValue: String!): Contact!
  # Editor. Deletes a custom attribute value from the contact.
  contactAttributeDelete(id: ContactID!, attrName: AttributeName!): Contact!
  # Editor. A free-text note shown on the contact card and in bookings. Errors: ContactNoteTooLong
  contactSetNote(id: ContactID!, note: String): Contact!
  # Agent. The person must have access to the bot's contacts (AssigneeHasNoAccess).
  contactSetAssignee(id: ContactID!, assigneeID: UserAccountID!): Contact!
  # Agent. Hands the contact to Fuely AI.
  contactSetFuelyAIAssignee(id: ContactID!): Contact!
  # Agent.
  contactRemoveAssignee(id: ContactID!): Contact!
  # Agent. Moves the contact on the Leads board.
  contactSetSalesStage(id: ContactID!, salesStageV2: SalesStageV2!): Contact!
  # Editor. Creates a WhatsApp contact by phone number for a calendar booking (source: CalendarBooking).
  whatsappContactCreateV2(botID: BotID!, data: WhatsappContactCreateInput!): Contact!
}
```

A virtual user can assign a contact to itself: pass its own `currentUser.id` as `assigneeID`.

Default values of bot attributes apply to every contact that has no value of its own (**Editor**):

```graphql
type Mutation {
  botAttributeCreateDefaultVal(botID: BotID!, attributeName: AttributeName!, defaultValue: String!): Bot!
  botAttributeUpdateDefaultVal(botID: BotID!, attributeName: AttributeName!, defaultValue: String!): Bot!
  botAttributeDeleteDefaultVal(botID: BotID!, attributeName: AttributeName!): Bot!
}
```

## How to read deals by sales stage [#how-to-read-deals-by-sales-stage]

The Leads board groups contacts by sales stage. Both queries work for **Agent**:

```graphql
type Bot {
  # Contacts in one stage, filtered by assignee (Any, Unassigned, FuelyAI or a specific AssigneeID).
  contactDealsConnection(
    first: Int!
    before: ContactSearchCursor
    after: ContactSearchCursor
    assigneeFilter: ContactAssigneeFilter!
    salesStageV2Filter: SalesStageV2!
  ): ContactConnection!
  # Number of contacts in each stage, optionally limited by the time of the last stage change.
  contactDealsByStages(filter: DealsByStagesFilter!): TotalsByStages!
}
```

## How to export and import contacts as CSV [#how-to-export-and-import-contacts-as-csv]

Exports and imports run in the background and report progress through a [task or a subscription](https://chatfuel.com/docs/public-api/files-and-tasks).

```graphql
type Mutation {
  # Agent. Export by segment; pass an empty attributes list to export all attributes.
  # Errors: CSVContactExportAlreadyInProgress
  csvContactExportStartBySegment(botID: BotID!, platforms: [Platform!]!, segment: SegmentInput, attributes: [AttributeName!]!): Task!
  # Agent. Export a list of contacts. Errors: CSVContactExportInvalidContactIDsCount
  csvContactExportStartByIDsList(botID: BotID!, contactIDs: [ContactID!]!, attributes: [AttributeName!]!): Task!
  # Agent. Cancels an export; the task status changes to cancelled shortly after.
  csvContactExportCancel(botID: BotID!, id: String!): Bot!

  # Editor. 1) upload the CSV file, 2) create the import, 3) map columns, 4) start it.
  csvContactImportCreate(botID: BotID!, fileID: FileID!, platform: Platform!, locale: DashboardLocale!): CSVContactImport!
  csvContactImportUpdateFile(botID: BotID!, id: CSVContactImportID!, fileID: FileID!): CSVContactImport!
  csvContactImportUpdateColumns(botID: BotID!, id: CSVContactImportID!, request: CSVContactImportColumnsUpdate!): CSVContactImport!
  # Errors: ContactScopeNotConnected if the channel, for example the WhatsApp number, is not connected.
  csvContactImportStart(botID: BotID!, id: CSVContactImportID!): CSVContactImport!
}
```

When an export task reaches the `Finished` status, its `data` is a `CSVContactsExport` whose `file { url }` is the CSV to download. `bot.lastActiveCSVContactsExportTask` (**Agent**) returns a running export, and `bot.latestCSVContactsImport(platform:)` (**Editor**) the last import with its counters and errors.

## Contact subscriptions [#contact-subscriptions]

```graphql
type Subscription {
  # Agent. Any change of one contact.
  contactUpdated(botID: BotID!, contactID: ContactID!): Contact
  # Agent. Changes on the Leads board for the given assignee filter.
  contactsDealUpdates(botID: BotID!, assigneeFilter: ContactAssigneeFilter!): ContactsDealUpdate
  contactsDealsPageIndicatorUpdates(botID: BotID!): DealsPageIndicatorUpdate
  # Editor. Progress of a CSV import.
  csvContactImportUpdated(botID: BotID!, id: CSVContactImportID!): CSVContactImport!
}
```

## Common issues [#common-issues]

### "NotEnoughPermissions" when an Agent edits a contact [#notenoughpermissions-when-an-agent-edits-a-contact]

Agents can assign contacts and change sales stages, but changing names, attributes and notes needs an Editor. If an Editor also gets this error, the bot's role settings may limit it to contacts assigned to it, as described in [team and roles](https://chatfuel.com/docs/public-api/team-and-roles).

### "SystemAttributeUpdateNotAllowed" [#systemattributeupdatenotallowed]

System attributes such as the phone number or the channel's profile data are set by Chatfuel. Store your own data in custom attributes.

### "ContactDoesNotExist" [#contactdoesnotexist]

The contact ID is wrong or belongs to another bot. Contact IDs are bot-specific; read them from `contactsConnection` of the same bot.
