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

# Update a contact



## OpenAPI

````yaml /api-reference/openapi.json patch /v1/contacts/{contact_id}
openapi: 3.1.0
info:
  title: Ciarem API
  version: v1
  description: >
    The Ciarem public API. Authenticate every request with an org-bound API key

    (created in Settings → API keys; Scale plan or free trial) sent as a bearer
    token:

    `Authorization: Bearer oak_…`.


    Compatibility: this is an early, fast-moving API. Most changes are

    additive (new endpoints, new optional params, new response fields, new

    enum/string values) — clients must tolerate unknown fields and unknown

    string values. Every contract change is listed in the changelog at

    help.ciarem.ai.
servers:
  - url: https://api.ciarem.ai
security:
  - apiKey: []
tags:
  - name: Account
    description: The organization behind the API key.
  - name: Contacts
    description: People in the CRM — create, read, update, delete.
  - name: CRM
    description: 'The shape of the CRM: custom properties and funnel stages.'
  - name: Conversations
    description: Read the message history of a conversation.
  - name: WhatsApp numbers
    description: The connected numbers a send can go out from.
  - name: WhatsApp templates
    description: Message templates, and what each one needs filled before it can be sent.
  - name: Broadcasts
    description: Send a template, and follow what happened to every recipient.
paths:
  /v1/contacts/{contact_id}:
    patch:
      tags:
        - Contacts
      summary: Update a contact
      operationId: patch_endpoint_v1_contacts__contact_id__patch
      parameters:
        - name: contact_id
          in: path
          required: true
          schema:
            type: string
            title: Contact Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicContactPatch'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicContact'
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
        '401':
          description: >-
            No usable credential: `missing_bearer_token` (no `Authorization:
            Bearer`) or `invalid_api_key` (unknown or revoked key).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '403':
          description: >-
            The credential is real but not allowed here: `api_key_required` (a
            session token rather than an `oak_` key), `scale_plan_required` (a
            live plan below Scale — upgrade to Scale), `trial_expired` (the free
            trial ended without a card — subscribe to keep the key working), or
            `subscription_required` (the subscription is canceled or suspended).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '409':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicDuplicateContactError'
          description: Conflict
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '429':
          description: >-
            Over the organization's request budget (`rate_limited`).
            `Retry-After` says how many seconds to wait.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
          headers:
            Retry-After:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
components:
  schemas:
    PublicContactPatch:
      properties:
        first_name:
          anyOf:
            - type: string
            - type: 'null'
          title: First Name
        last_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Name
        email:
          anyOf:
            - type: string
            - type: 'null'
          title: Email
        phone:
          anyOf:
            - type: string
              pattern: ^\+[1-9]\d{1,14}$
            - type: 'null'
          title: Phone
        country:
          anyOf:
            - type: string
              pattern: ^[A-Z]{2}$
            - type: 'null'
          title: Country
        city:
          anyOf:
            - type: string
              maxLength: 120
              minLength: 1
            - type: 'null'
          title: City
        instagram:
          anyOf:
            - type: string
              pattern: ^[a-zA-Z0-9._]{1,30}$
            - type: 'null'
          title: Instagram
        origin:
          anyOf:
            - $ref: '#/components/schemas/ContactOrigin'
            - type: 'null'
        sub_origin_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Sub Origin Id
        funnel_stage_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Funnel Stage Id
        owner_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Owner Id
        properties:
          anyOf:
            - additionalProperties:
                anyOf:
                  - $ref: '#/components/schemas/PropertyValueShape'
                  - type: 'null'
              type: object
            - type: 'null'
          title: Properties
      additionalProperties: false
      type: object
      title: PublicContactPatch
    PublicContact:
      properties:
        id:
          type: string
          title: Id
        first_name:
          anyOf:
            - type: string
            - type: 'null'
          title: First Name
        last_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Name
        email:
          anyOf:
            - type: string
            - type: 'null'
          title: Email
        phone:
          anyOf:
            - type: string
            - type: 'null'
          title: Phone
        country:
          anyOf:
            - type: string
            - type: 'null'
          title: Country
        city:
          anyOf:
            - type: string
            - type: 'null'
          title: City
        instagram:
          anyOf:
            - type: string
            - type: 'null'
          title: Instagram
        messenger:
          anyOf:
            - type: string
            - type: 'null'
          title: Messenger
        whatsapp_username:
          anyOf:
            - type: string
            - type: 'null'
          title: Whatsapp Username
        origin:
          anyOf:
            - $ref: '#/components/schemas/ContactOrigin'
            - type: 'null'
        sub_origin_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Sub Origin Id
        whatsapp_status:
          $ref: '#/components/schemas/WhatsappStatus'
        funnel_stage_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Funnel Stage Id
        owner_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Owner Id
        properties:
          additionalProperties:
            $ref: '#/components/schemas/PropertyValueShape'
          type: object
          title: Properties
        last_contacted_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Last Contacted At
        created_at:
          type: string
          format: date-time
          title: Created At
        updated_at:
          type: string
          format: date-time
          title: Updated At
      type: object
      required:
        - id
        - first_name
        - last_name
        - email
        - phone
        - country
        - city
        - instagram
        - messenger
        - whatsapp_username
        - origin
        - sub_origin_id
        - whatsapp_status
        - funnel_stage_id
        - owner_id
        - last_contacted_at
        - created_at
        - updated_at
      title: PublicContact
    PublicError:
      description: An error whose `detail` is a stable, machine-readable code.
      properties:
        detail:
          title: Detail
          type: string
      required:
        - detail
      title: PublicError
      type: object
    PublicDuplicateContactError:
      properties:
        detail:
          $ref: '#/components/schemas/PublicDuplicateContact'
      type: object
      required:
        - detail
      title: PublicDuplicateContactError
      description: The 409 response body as it appears on the wire.
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    ContactOrigin:
      type: string
      enum:
        - SOCIAL
        - ADS
        - WEB
        - EVENT
        - REFERRAL
        - OUTBOUND
        - IMPORT
        - MANUAL
        - API
        - OTHER
      title: ContactOrigin
      description: >-
        Top-level acquisition category — *how* a contact entered the CRM.


        Set once at first touch and never overwritten: a contact who joins the

        waitlist (`WEB`) and later messages on WhatsApp keeps `WEB`; the channel

        they messaged on lives on their `ContactHandle`, not here. The category
        is

        a fixed, platform-owned vocabulary so analytics roll-ups, the AI prompt,

        and inbound auto-assignment stay stable across orgs. The *specific*
        named

        source within a category (e.g. "Waitlist", "SaaStr 2026", "WhatsApp") is

        org-customizable — see the `contact_sub_origins` table. NULL for legacy

        contacts whose origin predates this field. Replaces the old
        conversational-

        only `ContactSource` (WHATSAPP/INSTAGRAM/MESSENGER), which collapsed

        acquisition and channel into one field.
    PropertyValueShape:
      properties:
        property_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Property Id
        kind:
          $ref: '#/components/schemas/CrmPropertyKind'
        value_text:
          anyOf:
            - type: string
            - type: 'null'
          title: Value Text
        value_number:
          anyOf:
            - type: number
            - type: 'null'
          title: Value Number
        value_bool:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Value Bool
        value_date:
          anyOf:
            - type: string
              format: date
            - type: 'null'
          title: Value Date
        value_json:
          anyOf:
            - {}
            - type: 'null'
          title: Value Json
      type: object
      required:
        - kind
      title: PropertyValueShape
      description: |-
        A custom property value as projected onto / read off a contact.

        Mirrors the typed-column layout in `crm_property_values` so the
        consumer can dispatch on `kind` and pick the matching `value_*` slot.
        For PATCH input the writer ignores columns irrelevant to the kind.
    WhatsappStatus:
      type: string
      enum:
        - verified
        - unverified
        - unavailable
      title: WhatsappStatus
      description: |-
        WhatsApp presence status for a contact's phone number.

        A new contact starts as `UNVERIFIED` when a phone is on file or
        `UNAVAILABLE` when none is. The AI agent flips `UNVERIFIED` →
        `VERIFIED` on a successful send, or → `UNAVAILABLE` on a 404 /
        not-on-whatsapp response. Direct patching is not exposed; the
        write path is purely service-layer derivation.
    PublicDuplicateContact:
      properties:
        code:
          type: string
          title: Code
          default: duplicate_contact
        field:
          type: string
          enum:
            - email
            - phone
            - instagram
          title: Field
        conflicting_contact_id:
          type: string
          title: Conflicting Contact Id
        first_name:
          anyOf:
            - type: string
            - type: 'null'
          title: First Name
        last_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Name
        email:
          anyOf:
            - type: string
            - type: 'null'
          title: Email
        phone:
          anyOf:
            - type: string
            - type: 'null'
          title: Phone
        instagram:
          anyOf:
            - type: string
            - type: 'null'
          title: Instagram
      type: object
      required:
        - field
        - conflicting_contact_id
      title: PublicDuplicateContact
      description: >-
        The `detail` of the 409 when a create/patch collides on a unique

        identifier (wire shape: `{"detail": {…}}` — see
        PublicDuplicateContactError).
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
    CrmPropertyKind:
      type: string
      enum:
        - TEXT
        - NUMBER
        - RADIO
        - DROPDOWN
        - BOOLEAN
        - DATE
        - TEXTAREA
        - MULTI_SELECT
        - EMAIL
        - MONEY
        - FILE
        - URL
        - MULTI_FILE
      title: CrmPropertyKind
      description: >-
        Type of a user-defined custom property on a contact.


        The order mirrors the Postgres `crm_property_kind_enum`: TEXTAREA was

        appended in 0014 (alongside MULTI_SELECT), EMAIL and MONEY in 0025, FILE

        in 0065, URL in 0167, MULTI_FILE in 0186. `EMAIL` and `URL` store into

        `value_text` (like TEXT) and `MONEY` into `value_number` (like NUMBER) —

        they're presentation/validation refinements of those base columns, not
        new

        storage shapes. `FILE` and `MULTI_FILE` both store an array of S3 file

        references in `value_json` (see `ciarem_crm.file_property`); they differ

        only in cardinality — FILE holds at most one file, MULTI_FILE a capped

        gallery. Pre-0186, FILE meant the gallery; 0186 renamed those rows to

        MULTI_FILE and repurposed FILE as the single-file kind (it was

        catalogs-only, so no contact data existed). The bytes live in the

        `record-media` bucket.
  headers:
    X-RateLimit-Limit:
      description: Requests allowed per window, shared across the org's keys.
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Requests left in the current window.
      schema:
        type: integer
    X-RateLimit-Reset:
      description: When the window resets, as a Unix timestamp in seconds.
      schema:
        type: integer
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: 'An org API key: `Authorization: Bearer oak_…`.'

````