> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kaisupport.com/llms.txt
> Use this file to discover all available pages before exploring further.

# List contacts

> Returns one page of contacts, oldest change first.

This endpoint is also the export. Set `limit` to 500 and follow
`next_cursor` until it is `null`.

To read only what changed, send `updated_since`. The order makes this
safe: a contact that changes during the export moves forward in the
order, not backward, so the export cannot miss it.




## OpenAPI

````yaml /openapi.yaml get /contacts
openapi: 3.1.0
info:
  title: Kai Contacts API
  version: 1.0.0
  description: |
    Push, update, assign and export the contacts of one Kai workspace.

    Every request needs an API key. Make a key in Kai, under
    **Settings → API**. Send the key in the `Authorization` header.

    The API works on one workspace. The key decides which workspace, so no
    request carries a workspace id.
  contact:
    name: Kai support
    url: https://kaisupport.com
servers:
  - url: https://kaisupport.com/api/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Contacts
    description: Customer profiles, their attributes and their account owners.
  - name: Attributes
    description: The attributes that the workspace keeps on every contact.
  - name: Members
    description: The teammates of the workspace. Read-only.
paths:
  /contacts:
    get:
      tags:
        - Contacts
      summary: List contacts
      description: |
        Returns one page of contacts, oldest change first.

        This endpoint is also the export. Set `limit` to 500 and follow
        `next_cursor` until it is `null`.

        To read only what changed, send `updated_since`. The order makes this
        safe: a contact that changes during the export moves forward in the
        order, not backward, so the export cannot miss it.
      operationId: listContacts
      parameters:
        - name: limit
          in: query
          description: Contacts per page, from 1 to 500. The default is 50.
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
        - name: cursor
          in: query
          description: The `next_cursor` value of the last response.
          schema:
            type: string
        - name: search
          in: query
          description: Matches a part of the name, email, phone or external id.
          schema:
            type: string
        - name: owner
          in: query
          description: |
            A member id to list the contacts of one account owner. Send `none`
            for the contacts that have no owner.
          schema:
            type: string
        - name: updated_since
          in: query
          description: An ISO 8601 timestamp. Returns contacts changed at or after it.
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: One page of contacts.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - next_cursor
                  - has_more
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Contact'
                  next_cursor:
                    type:
                      - string
                      - 'null'
                    description: >-
                      Send this as `cursor` to get the next page. `null` at the
                      end.
                  has_more:
                    type: boolean
              example:
                data:
                  - id: 4f1d6f2a-6a1e-4a0b-9c53-2a9a1f0c77d1
                    external_id: DK-1042
                    name: Ayşe Yılmaz
                    email: ayse@example.com
                    phone: '+905321112233'
                    note: The parent buys. The student is in year 12.
                    attributes:
                      package: Tam Destek
                      exam_year: 2027
                    owner:
                      member_id: 2c9a0d51-6f4b-4b0d-8f2a-1d3c5e7a9b11
                      name: Baran Demir
                      email: baran@example.com
                    created_at: '2026-03-02T09:14:00.000Z'
                    updated_at: '2026-08-05T18:20:11.000Z'
                next_cursor: MjAyNi0wOC0wNVQxODoyMDoxMS4wMDBafDRmMWQ2ZjJh
                has_more: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
components:
  schemas:
    Contact:
      type: object
      required:
        - id
        - attributes
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
          description: The Kai contact id.
        external_id:
          type:
            - string
            - 'null'
          description: Your own id for this person. It is unique in the workspace.
        name:
          type:
            - string
            - 'null'
        email:
          type:
            - string
            - 'null'
        phone:
          type:
            - string
            - 'null'
        note:
          type:
            - string
            - 'null'
          description: A note for teammates. Kai does not send it to a customer.
        attributes:
          type: object
          description: The workspace attributes of this contact, by API name.
          additionalProperties:
            type:
              - string
              - number
              - boolean
              - 'null'
        owner:
          type:
            - object
            - 'null'
          description: The account owner, or `null`.
          properties:
            member_id:
              type: string
              format: uuid
            name:
              type:
                - string
                - 'null'
            email:
              type:
                - string
                - 'null'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Error:
      type: object
      properties:
        error:
          $ref: '#/components/schemas/ErrorBody'
    ErrorBody:
      type: object
      properties:
        type:
          type: string
          description: A stable machine name for the error.
        message:
          type: string
          description: One sentence for a human reader.
        details:
          description: The attributes or the identities that caused the error.
  responses:
    BadRequest:
      description: Kai could not read the request.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: invalid_request
              message: >-
                Send at least one of "external_id", "email", "phone" or
                "identities" so the contact can be matched.
    Unauthorized:
      description: The API key is missing or not valid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: unauthorized
              message: That API key is not valid.
    Forbidden:
      description: The API key does not have the permission that this endpoint needs.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: forbidden
              message: This key does not have the "contacts:write" permission.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Send your API key as `Authorization: Bearer kai_live_...`.

        Make and revoke keys in Kai, under **Settings → API**. Kai stores only
        a hash of the key, so the full key is shown one time.

````