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

# Update a contact



## OpenAPI

````yaml PATCH /v1/contact/{id}
openapi: 3.0.0
info:
  title: Gojiberry AI - External API
  description: >-

    # Gojiberry AI External API


    This API allows you to programmatically access your Gojiberry AI data and
    integrate it with your applications.


    ## How it works


    Gojiberry runs your outreach as a pipeline of three building blocks:


    1. **Source agent — the lead finder.** A source agent continuously sources
    leads matching your targeting criteria (job titles, industries, locations,
    signals, ...) and scores them with AI.

    2. **List — where the leads land.** Every source agent is linked to a list
    (`listId` is required when creating an agent): the leads it finds are
    imported as contacts into that list. Lists can also be filled by importing
    contacts yourself.

    3. **Campaign — the outreach.** A campaign uses one or more lists as its
    lead source (`listIds` is required when creating a campaign) and runs its
    steps (LinkedIn invitations, messages, emails, ...) on the contacts of those
    lists, sending through your LinkedIn and/or email seats. Once activated, the
    campaign runs the outreach automatically on a daily basis.


    **Terminology.** In the Gojiberry platform, an **Agent** is a *campaign
    agent* — the campaign that performs the outreach. A **source agent** is the
    lead finder that sources the leads. The two are linked through a **list**:
    the source agent imports the leads it finds into the list, and the campaign
    agent reaches out to the contacts of that list.


    **Best practice: create a Full Cycle agent.** Always create the three
    resources in this order — first the list, then the source agent that fills
    it, then the campaign that reaches out to it. Together, the list + source
    agent + campaign agent form a **Full Cycle agent**: leads are sourced and
    outreached automatically, end to end. If you create a source agent without a
    campaign agent, it shows up as a **legacy agent** in the platform: it only
    sources leads and does **no outreach**. So it's best practice to always
    create all three.


    A typical setup through the API:


    ```text

    POST /v1/list                  # 1. create the list

    POST /v1/agent                 # 2. create the source agent (lead finder)
    linked to that list (listId)

    POST /v1/campaign              # 3. create the campaign agent using that
    list as lead source (listIds)

    GET  /v1/campaign/{id}/logs    # 4. follow the outreach activity

    ```


    ## Authentication


    All API requests require authentication using a Bearer token. To get your
    API key:


    1. **Log in to your Gojiberry AI account** at
    [https://app.gojiberry.ai](https://app.gojiberry.ai)

    2. **Navigate to Settings** → **API** section

    3. **Click "Create API Key"** to generate a new token

    4. **Copy the API key** when it's displayed (you won't be able to see it
    again)

    5. **Use the token** in your requests by adding the header: `Authorization:
    Bearer YOUR_API_KEY`


    ## Impersonation (organization owners)


    If your API key belongs to the **owner of an organization**, you can perform
    any API call on behalf of another member of your organization by adding the
    `x-impersonate-user-id` header with the member's user ID:


    ```bash

    # Get the contacts of the organization member with user ID 123

    curl -H "Authorization: Bearer YOUR_API_KEY" \
         -H "x-impersonate-user-id: 123" \
         https://ext.gojiberry.ai/v1/contact
    ```


    When impersonating, the request behaves exactly as if it was made by that
    member: you read and write their data (contacts, campaigns, lists, unibox,
    etc.).


    Rules:

    - Your API key must belong to the **organization owner**; other members
    cannot impersonate.

    - The impersonated user must belong to the **same organization** as the API
    key user.

    - Requests that don't meet these rules are rejected with a `401
    Unauthorized`.


    Tip: use `GET /v1/organization/members` to list the members of your
    organization and their user IDs, and `GET /v1/user/me` to verify which user
    the current request acts as.


    ## Rate Limits


    - **100 requests per minute** per account. Short bursts above this are
    tolerated.

    - **100,000 requests per day** per account.

    - Limits are shared by all API keys and AI app (MCP) connections of the same
    user, including calls made on behalf of organization members with
    `x-impersonate-user-id`.

    - Requests over a limit return `429 Too Many Requests` with a `Retry-After`
    header (in seconds). Wait that long before retrying.

    - Some endpoints have their own lower limit, stated in their description.


    ## Base URL

    - **Production**: `https://ext.gojiberry.ai`


    ## Endpoints


    ### Contacts

    - `GET /v1/contact` - Get all contacts with filtering and pagination

    - `GET /v1/contact/{id}` - Get a specific contact

    - `GET /v1/contact/health` - Health check endpoint


    ## Example Usage


    ```bash

    # Get all contacts

    curl -H "Authorization: Bearer YOUR_API_KEY" \
         https://ext.gojiberry.ai/v1/contact

    # Get contacts with filtering

    curl -H "Authorization: Bearer YOUR_API_KEY" \
         "https://ext.gojiberry.ai/v1/contact?search=john&agent=1&dateFrom=2024-01-01"

    # Get a specific contact

    curl -H "Authorization: Bearer YOUR_API_KEY" \
         https://ext.gojiberry.ai/v1/contact/123
    ```


    ## Webhooks


    Gojiberry allows you to receive real-time notifications when new contacts
    are created or updated by configuring a webhook URL.


    ### How to Configure Webhooks


    1. **Log in to your Gojiberry AI account** at
    [https://app.gojiberry.ai](https://app.gojiberry.ai)

    2. **Navigate to the Integrations page** at
    [https://app.gojiberry.ai/integrations](https://app.gojiberry.ai/integrations)

    3. **Add a Webhook integration** by clicking the Webhook option

    4. **Enter your webhook URL** where you want to receive notifications

    5. **Save the configuration**


    Once configured, Gojiberry will send POST requests to your webhook URL
    whenever:

    - A new contact is created

    - A contact is updated

    - Other relevant events occur in your account


    The webhook payload will include the full contact data in JSON format. Each
    webhook request includes a custom header `x-gojiberry-user-id` containing
    your user ID for verification purposes.


    ### Verifying Webhook Signatures (optional)


    You can optionally configure a signing secret on your Webhook integration.
    When a secret is set, every webhook request also includes:


    - `x-gojiberry-timestamp`: the Unix timestamp (in seconds) at which the
    request was sent

    - `x-gojiberry-signature`: `sha256=` followed by the hex-encoded HMAC-SHA256
    of `{timestamp}.{rawBody}` computed with your signing secret


    To verify a request:


    1. Read the raw request body as a string (do not re-serialize parsed JSON).

    2. Concatenate the value of `x-gojiberry-timestamp`, a `.` character, and
    the raw body.

    3. Compute the HMAC-SHA256 of that string using your signing secret,
    hex-encoded, and prefix it with `sha256=`.

    4. Compare it to `x-gojiberry-signature` using a constant-time comparison.

    5. Reject requests whose timestamp is too old (we recommend a 5 minute
    tolerance) to prevent replay attacks.


    Example in Node.js:


    ```js

    const crypto = require('crypto');


    function verifyGojiberrySignature(rawBody, headers, secret) {
      const timestamp = headers['x-gojiberry-timestamp'];
      const signature = headers['x-gojiberry-signature'];
      if (!timestamp || !signature) return false;
      if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
      const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex');
      return expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
    }

    ```


    If no signing secret is configured, these headers are not sent.


    ## Support


    For API support, please contact us at
    [hello@gojiberry.ai](mailto:hello@gojiberry.ai)
          
  version: '1.0'
  contact: {}
servers:
  - url: https://ext.gojiberry.ai
    description: Production
security: []
tags: []
paths:
  /v1/contact/{id}:
    patch:
      tags:
        - Contacts
      summary: Update a contact
      operationId: ContactExternalController_update
      parameters:
        - name: x-impersonate-user-id
          in: header
          description: >-
            ID of an organization member to act on behalf of. The API key user
            must be the owner of the organization and the target user must
            belong to the same organization.
          required: false
          schema:
            type: string
        - name: id
          required: true
          in: path
          schema:
            type: number
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateContactExternalDto'
      responses:
        '200':
          description: Contact successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '404':
          description: Contact not found
      security:
        - bearer: []
components:
  schemas:
    UpdateContactExternalDto:
      type: object
      properties:
        firstName:
          type: string
          description: First name of the contact
        lastName:
          type: string
          description: Last name of the contact
        profileUrl:
          type: string
          description: LinkedIn profile URL
        profileId:
          type: string
          description: LinkedIn profile ID
        profileBaseline:
          type: string
          description: Profile baseline information
        location:
          type: string
          description: Location of the contact
        companyLocation:
          type: string
          description: Location of the contact company
        jobTitle:
          type: string
          description: Job title of the contact
        positionDates:
          type: string
          description: Position dates information
        company:
          type: string
          description: Company name
        companySize:
          type: string
          description: Company size
        companyUrl:
          type: string
          description: LinkedIn company URL
        website:
          type: string
          description: Website URL
        industry:
          type: string
          description: Industry
        email:
          type: string
          description: Primary email address
        picture:
          type: string
          description: Profile picture URL
        openToWork:
          type: boolean
          description: Open to work status
        linkedinIdentifier:
          type: string
          description: LinkedIn identifier for the profile
        intent:
          type: string
          description: Intent or activity description for the contact
        fit:
          type: string
          description: Contact fit classification
          enum:
            - qualified
            - unknown
            - out-of-scope
        readyForCampaign:
          type: boolean
          description: >-
            Mainly used for review mode campaigns: setting this to true accepts
            the lead for the scheduled queue, so it is picked up at the next
            campaign launch without manual review. Also set by the AI agent when
            a contact should be scheduled.
        state:
          type: string
          description: >-
            Contact state. Set to "paused" to stop a contact from progressing in
            the campaign. Send null to clear the state. Possible values:
            "paused" (stops campaign progression), "finished" (completed all
            steps), "1stnetwork" (1st degree LinkedIn connection), "excluded"
            (excluded from all campaigns), "answered" (replied to a message).
          enum:
            - finished
            - paused
            - 1stnetwork
            - excluded
            - answered
          nullable: true
        personalizedMessages:
          description: >-
            Personalized messages for each campaign step. This array REPLACES
            the stored messages: always resend every step you want to keep, with
            all of its fields (content, subject, parts). Any step or field left
            out of the payload is erased.
          type: array
          items:
            $ref: '#/components/schemas/PersonalizedMessageDto'
    Contact:
      type: object
      properties:
        id:
          type: number
          description: Unique identifier for the contact
        firstName:
          type: string
          description: First name of the contact
        lastName:
          type: string
          description: Last name of the contact
        fullName:
          type: string
          description: Full name (auto-generated from firstName and lastName)
          readOnly: true
        profileBaseline:
          type: string
          description: Profile baseline information
        location:
          type: string
          description: Location of the contact
        companyLocation:
          type: string
          description: Location of the contact company
        jobTitle:
          type: string
          description: Job title of the contact
        positionDates:
          type: string
          description: Position dates information
        company:
          type: string
          description: Company name
        companySize:
          type: string
          description: Company size
        companyUrl:
          type: string
          description: Company URL
        website:
          type: string
          description: Website URL
        industry:
          type: string
          description: Industry
        email:
          type: string
          description: Primary email address
        email_2:
          type: string
          description: Secondary email address
        email_3:
          type: string
          description: Tertiary email address
        secondEmail:
          type: string
          description: Second email alias
        thirdEmail:
          type: string
          description: Third email alias
        phone:
          type: string
          description: Primary phone number
        phone_2:
          type: string
          description: Secondary phone number
        phone_3:
          type: string
          description: Tertiary phone number
        secondPhone:
          type: string
          description: Second phone alias
        thirdPhone:
          type: string
          description: Third phone alias
        profileId:
          type: string
          description: LinkedIn profile ID
        linkedinMemberId:
          type: number
          description: Numeric LinkedIn member ID decoded from the profile URN
        profileUrl:
          type: string
          description: LinkedIn profile URL
        picture:
          type: string
          description: Profile picture URL
        openToWork:
          type: boolean
          description: Open to work status
        skills:
          description: Array of skills
          type: array
          items:
            type: string
        linkedinIdentifier:
          type: string
          description: LinkedIn identifier for the profile
        note:
          type: string
          description: Additional notes about the contact
        user_note:
          type: string
          description: User personal notes about the contact
        intent:
          type: string
          description: Intent or activity description
        intent_scoring:
          type: number
          description: AI scoring for intent relevance between 0 and 3
        total_scoring:
          type: number
          description: Total scoring (intent_scoring + scoring)
        intent_type:
          type: object
          description: Type or category of the intent
        intent_keyword:
          type: string
          description: Keyword extracted from the intent
        scoring:
          type: number
          description: AI scoring result between 0 and 1 (0 = no match, 1 = perfect match)
        score_reasoning:
          type: string
          description: Reasoning for the AI scoring result
        bounced:
          type: boolean
          description: Whether the contact email has bounced
          default: false
        unsubscribed:
          type: boolean
          description: Whether the contact has unsubscribed from emails
          default: false
        redListed:
          type: boolean
          description: Whether the contact is on the global GDPR red list (do-not-contact)
          default: false
        blocked:
          type: boolean
          description: Whether the contact matches the organization people blocklist
          default: false
        companyBlocked:
          type: boolean
          description: >-
            Set once a campaign pass skipped the contact because its company is
            on the organization blocklist
          default: false
        emailEnriched:
          type: boolean
          description: Whether email has been enriched
          default: false
        phoneEnriched:
          type: boolean
          description: Whether phone has been enriched
          default: false
        enrichingEmail:
          type: boolean
          description: Whether an email enrichment job currently holds the billing lease
          default: false
        enrichingPhone:
          type: boolean
          description: Whether a phone enrichment job currently holds the billing lease
          default: false
        fit:
          type: string
          description: Contact fit classification
          enum:
            - qualified
            - unknown
            - out-of-scope
        rejectedAt:
          format: date-time
          type: string
          description: When this contact was rejected
          nullable: true
        rejectedByUserId:
          type: number
          description: User who rejected this contact
          nullable: true
        rejectionReason:
          type: string
          description: Stable rejection reason
          nullable: true
        rejectionReasonText:
          type: string
          description: Free-text reason when Other was submitted with text
          nullable: true
        deepResearchStartedAt:
          format: date-time
          type: string
          description: When the latest deep research started for this contact
        deepResearchEndedAt:
          format: date-time
          type: string
          description: When the latest deep research ended for this contact
        email_template:
          type: object
          description: Email template in JSON format with subject and body
        linkedin_template:
          type: string
          description: LinkedIn template text
        personalizedMessages:
          description: Personalized messages for the contact
          type: array
          items:
            type: string
        destinationStatus:
          description: Destination sync status information
          type: array
          items:
            type: string
        customFields:
          type: object
          description: >-
            Custom fields imported from CSV, keyed by normalized snake_case name
            (rendered with the custom_ prefix)
        campaignStatus:
          description: >-
            Campaign status information. Useful to know what is the achieved
            step of the contact in the campaign — i.e. how far the contact has
            progressed through the campaign's sequence. The stepNumber matches
            the campaign's step numbers (the steps defined on the campaign), so
            a stepNumber of 2 means the contact has reached step 2 of that
            campaign.
          type: array
          items:
            type: string
        signals:
          description: >-
            Every signal recorded for the contact, most recent first (agents and
            LinkedIn pools)
          type: array
          items:
            type: string
        userId:
          type: number
          description: User ID who owns this contact
        user:
          description: Associated user account
          allOf:
            - $ref: '#/components/schemas/Account'
        agentId:
          type: number
          description: Associated agent ID
        agent:
          description: Associated agent
          allOf:
            - $ref: '#/components/schemas/Agent'
        listId:
          type: number
          description: Associated list ID
        list:
          description: Associated list
          allOf:
            - $ref: '#/components/schemas/List'
        readyForCampaign:
          type: boolean
          description: Ready for campaign flag
          default: false
        hasStartedCampaign:
          type: boolean
          description: Has started campaign flag
          default: false
        state:
          type: string
          description: Contact state
          enum:
            - finished
            - paused
            - 1stnetwork
            - excluded
            - onhold
        createdAt:
          format: date-time
          type: string
          description: Creation timestamp
        updatedAt:
          format: date-time
          type: string
          description: Last update timestamp
      required:
        - id
        - firstName
        - lastName
        - fullName
        - bounced
        - unsubscribed
        - redListed
        - blocked
        - companyBlocked
        - emailEnriched
        - phoneEnriched
        - enrichingEmail
        - enrichingPhone
        - userId
        - user
        - readyForCampaign
        - hasStartedCampaign
        - createdAt
        - updatedAt
    PersonalizedMessageDto:
      type: object
      properties:
        content:
          type: string
          description: Message content for this campaign step
        parts:
          description: Derived conversation parts when split is enabled
          type: array
          items:
            type: string
        subject:
          type: string
          description: >-
            Email subject for this campaign step. Only used when the step is an
            email step. When editing an email step, always resend the subject
            together with the content — even if only the content changed —
            otherwise the stored subject is erased.
        stepNumber:
          type: number
          description: >-
            Campaign step number this message applies to. Please refer to the
            campaign.steps array !
        stepId:
          type: string
          description: >-
            Server-managed stable id of the campaign step. Read-only: the server
            derives it from stepNumber on write, any sent value is ignored.
            Declared so stored messages can be resent as-is.
        audioUrl:
          type: string
          description: >-
            Server-managed audio of an AI voice message step, spoken in the
            account’s cloned voice. Read-only: generated voice messages are kept
            as stored and any sent value is ignored. Declared so stored messages
            can be resent as-is.
        aiVoiceId:
          type: number
          description: >-
            Server-managed id of the cloned voice that spoke `audioUrl`.
            Read-only: any sent value is ignored. Declared so stored messages
            can be resent as-is.
      required:
        - content
        - stepNumber
    Account:
      type: object
      properties: {}
    Agent:
      type: object
      properties:
        id:
          type: number
          description: Unique identifier
        name:
          type: string
          description: Display name of the agent
        targetJobTitles:
          description: List of target job titles to prospect. Part of the ICP.
          type: array
          items:
            type: string
        targetIndustries:
          description: List of target industries to prospect. Part of the ICP.
          type: array
          items:
            type: string
        targetCompanySizes:
          description: >-
            List of target company sizes (e.g. "1-10", "11-50"). Part of the
            ICP.
          type: array
          items:
            type: string
        targetLocations:
          description: List of target geographic locations. Part of the ICP.
          type: array
          items:
            type: string
        targetCompanyTypes:
          description: List of target company types (e.g. "Public", "Private")
          type: array
          items:
            type: string
        additionalCriteria:
          type: string
          description: Additional free-text targeting criteria
          nullable: true
        variables:
          description: >-
            Variables used by the agent to find high intent leads. (Keywords,
            Competitor Page URL, etc.)
          nullable: true
          type: array
          items:
            type: string
        ignoredCompanies:
          description: Company names to exclude from prospecting. Part of the ICP.
          nullable: true
          type: array
          items:
            type: string
        mandatoryKeywords:
          description: Keywords that must be present in a lead profile to be included
          nullable: true
          type: array
          items:
            type: string
        agentType:
          type: string
          description: 'Type of agent: "autopilot", "lookalike" or "website-visitor"'
          example: autopilot
        scriptInstalled:
          type: boolean
          description: >-
            Whether the visitor tracking script is installed on the website
            (website-visitor agents)
          default: false
        trackingScriptId:
          type: string
          description: >-
            Snitcher tracking script ID, used as the profileId in the tracking
            snippet (website-visitor agents)
          nullable: true
        maxCreditUsage:
          type: number
          description: Max number of credits per month
          nullable: true
          example: 200
        currentCreditUsage:
          type: number
          description: >-
            Credits consumed so far in the current period (e.g. website-visitor
            company identifications)
          default: 0
        lastRun:
          format: date-time
          type: string
          description: Timestamp of the last agent execution
          nullable: true
        paused:
          type: boolean
          description: >-
            Whether the agent is paused and will not run on its next scheduled
            time
          default: false
        excludeServiceProviders:
          type: boolean
          description: Whether to exclude service provider companies from prospecting
          default: false
        skipIcpFilter:
          type: boolean
          description: >-
            Whether to skip the ICP (Ideal Customer Profile) filter when
            sourcing leads
          default: false
        includeOpenToWorkProfiles:
          type: boolean
          description: Whether to include LinkedIn profiles marked as open to work
          default: false
        leadWaterfall:
          type: boolean
          description: >-
            Whether to use the lead waterfall strategy (cascade through multiple
            sources)
          default: true
        minLeadScore:
          type: number
          description: Minimum lead score threshold (0.00–1.00) required to include a lead
          nullable: true
          example: 0.75
        userId:
          type: number
          description: ID of the account (user) who owns this agent
        listId:
          type: number
          description: ID of the list this agent sources contacts into
          nullable: true
        createdAt:
          format: date-time
          type: string
          description: Creation timestamp
        updatedAt:
          format: date-time
          type: string
          description: Last update timestamp
      required:
        - id
        - name
        - targetJobTitles
        - targetIndustries
        - targetCompanySizes
        - targetLocations
        - targetCompanyTypes
        - agentType
        - scriptInstalled
        - currentCreditUsage
        - paused
        - excludeServiceProviders
        - skipIcpFilter
        - includeOpenToWorkProfiles
        - leadWaterfall
        - userId
        - createdAt
        - updatedAt
    List:
      type: object
      properties:
        id:
          type: number
          description: Unique identifier for the list
        name:
          type: string
          description: Name of the list
        userId:
          type: number
          description: User ID who owns this list
        user:
          description: Associated user account
          allOf:
            - $ref: '#/components/schemas/Account'
        campaignId:
          type: number
          description: Campaign ID associated with this list
        campaign:
          description: Associated campaign
          allOf:
            - $ref: '#/components/schemas/Campaign'
        customFields:
          description: >-
            Custom field definitions imported into this list (append-only union
            across imports)
          type: array
          items:
            type: string
        createdAt:
          format: date-time
          type: string
          description: Creation timestamp
        updatedAt:
          format: date-time
          type: string
          description: Last update timestamp
      required:
        - id
        - name
        - userId
        - user
        - createdAt
        - updatedAt
    Campaign:
      type: object
      properties:
        id:
          type: number
          description: Unique identifier for the campaign
        name:
          type: string
          description: Name of the campaign
        steps:
          type: array
          description: Campaign steps
          items:
            type: object
        userId:
          type: number
          description: User ID who owns this campaign
        active:
          type: boolean
          description: Whether the campaign is active
          default: true
        excludeFirstDegreeFromCampaigns:
          type: boolean
          description: Exclude first degree connections from campaigns
          default: true
        splitLinkedinMessagesIntoConversation:
          type: boolean
          description: >-
            When enabled, LinkedIn message steps may be sent as multiple shorter
            messages when it feels natural
          default: false
        isManual:
          type: boolean
          description: Whether the campaign is manual
          default: false
        skipInvitationAfterDays:
          type: number
          description: >-
            Number of days after which a pending invitation is skipped and only
            email steps are sent
        language:
          type: string
          description: Campaign language
          nullable: true
        tone:
          type: string
          description: Campaign tone
          enum:
            - professional
            - direct
            - conversational
        goal:
          type: string
          description: Campaign goal
          enum:
            - conversations
            - demos
        linkedinSeatId:
          type: number
          description: LinkedIn seat ID for this campaign
        createdAt:
          format: date-time
          type: string
          description: Creation timestamp
        updatedAt:
          format: date-time
          type: string
          description: Last update timestamp
      required:
        - id
        - name
        - userId
        - active
        - excludeFirstDegreeFromCampaigns
        - splitLinkedinMessagesIntoConversation
        - isManual
        - createdAt
        - updatedAt
  securitySchemes:
    bearer:
      scheme: bearer
      bearerFormat: JWT
      type: http
      description: 'API key from Settings → API. Send it as Authorization: Bearer <key>.'

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.