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

# Start a contact search

> Find everyone matching any of several personas at each of several companies, and collect them into one deduplicated contacts list.

This is the fan-out counterpart of [Search for people](/api-reference/people-search/search-people). That operation answers "the next 25 people matching one filter set" and returns them to you; this one runs `queries` x `company_linkedin_ids` searches in the background, merges what they find, removes duplicates, and keeps the result as a list. Use it when you have a target account list and the personas you sell to, and want everyone rather than a page.

**It returns as soon as the search has started**, not when it has finished — a wide one runs for minutes to hours. Poll [Get a contact search](/api-reference/contact-searches/get-contact-search) for `status.phase`, and read the people through [List the contacts in a contacts list](/api-reference/contacts-lists/list-contacts-list-contacts) with the `contacts_list_id` below. Contacts appear there as they are found, so you can read the list while it fills.

**Cost.** 1 credit per profile extracted, plus enrichment where you asked for it. A pair that matches nobody costs nothing, so the price is not the fan-out size — size it first with [Preview a contact search](/api-reference/contact-searches/preview-contact-search-count). The call is refused with 402 only when the account has no credits at all.



## OpenAPI

````yaml /reference/openapi.yaml post /v2/contact-searches
openapi: 3.1.0
info:
  title: Tamtam Public API
  version: 2.0.0
servers:
  - url: https://api.tamtam.ai/api
security: []
paths:
  /v2/contact-searches:
    post:
      tags:
        - Contact Searches
      summary: Start a contact search
      description: >-
        Find everyone matching any of several personas at each of several
        companies, and collect them into one deduplicated contacts list.


        This is the fan-out counterpart of [Search for
        people](/api-reference/people-search/search-people). That operation
        answers "the next 25 people matching one filter set" and returns them to
        you; this one runs `queries` x `company_linkedin_ids` searches in the
        background, merges what they find, removes duplicates, and keeps the
        result as a list. Use it when you have a target account list and the
        personas you sell to, and want everyone rather than a page.


        **It returns as soon as the search has started**, not when it has
        finished — a wide one runs for minutes to hours. Poll [Get a contact
        search](/api-reference/contact-searches/get-contact-search) for
        `status.phase`, and read the people through [List the contacts in a
        contacts
        list](/api-reference/contacts-lists/list-contacts-list-contacts) with
        the `contacts_list_id` below. Contacts appear there as they are found,
        so you can read the list while it fills.


        **Cost.** 1 credit per profile extracted, plus enrichment where you
        asked for it. A pair that matches nobody costs nothing, so the price is
        not the fan-out size — size it first with [Preview a contact
        search](/api-reference/contact-searches/preview-contact-search-count).
        The call is refused with 402 only when the account has no credits at
        all.
      operationId: create-contact-search
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateContactSearchInputBody'
        required: true
      responses:
        '202':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactSearchDto'
          description: Accepted
        default:
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorModel'
          description: Error
      security:
        - apikeyAuth: []
        - bearerAuth: []
components:
  schemas:
    CreateContactSearchInputBody:
      additionalProperties: false
      properties:
        company_linkedin_ids:
          description: >-
            LinkedIn company IDs to search, at least one. Every query runs at
            every company, so this multiplies the work: resolve them via [Search
            for a company](/api-reference/companies/search-companies) or read
            them off a companies list.
          examples:
            - - '104924588'
          items:
            type: string
          minItems: 1
          type:
            - array
            - 'null'
        destination_contacts_list_id:
          description: >-
            Deliver into a contacts list that already exists instead of creating
            one — 're-run into this list'. Any list you could add to by hand; a
            Tamtam-managed list is refused. Omitted, the search makes its own
            list and the account's standing list-building columns are attached
            to it, exactly as they would be in the app.
          type: string
        enrich_emails:
          description: >-
            Also look up a professional email for each extracted contact.
            Charged per enrichment on top of the extraction, and only for
            contacts that clear low_lead_score_threshold.
          type: boolean
        enrich_phones:
          description: >-
            Also look up a phone number for each extracted contact. Same
            charging and same lead-score cut as enrich_emails.
          type: boolean
        idempotency_key:
          description: >-
            Your own key for this submission, so a retried POST returns the
            search the first one started instead of starting a second. Omitted,
            every call starts a new search — which is what a fresh request
            usually means.


            A key is unique per account for good: reusing one more than 30
            seconds later is a 409, not a replay.
          type: string
        low_lead_score_threshold:
          description: >-
            Buy an email or a phone only for a contact whose Lead Score reaches
            this. Omitted, every extracted contact is enriched. Ignored when
            neither enrich_emails nor enrich_phones is set.
          examples:
            - 40
          format: int64
          type: integer
        max_profiles_per_company_persona:
          description: >-
            Cap on the profiles fetched for each (query, company) pair. Omitted,
            the search is exhaustive up to the platform maximum.


            This is a cap, not a target: a pair that matches nobody costs
            nothing, and 1 credit is charged per profile actually extracted.
          examples:
            - 20
          format: int64
          type: integer
        queries:
          description: >-
            The personas to look for, at least one. Each runs at every company
            and the results are merged into one list, deduplicated, so a person
            matching two queries appears once.
          items:
            $ref: '#/components/schemas/ContactSearchQueryInputDto'
          minItems: 1
          type:
            - array
            - 'null'
        user_email:
          description: >-
            Who to attribute the search to in the app. Freeform and opaque to
            us; an API key authenticates the workspace, not a person, so nothing
            is inferred when this is omitted.
          examples:
            - rep@acme.com
          type: string
      required:
        - company_linkedin_ids
        - queries
      type: object
    ContactSearchDto:
      additionalProperties: false
      properties:
        already_existed:
          description: >-
            Only on a create: true when your idempotency_key matched a
            submission already in flight, so this is that search rather than a
            new one.
          type: boolean
        company_linkedin_ids:
          items:
            type: string
          type:
            - array
            - 'null'
        contacts_list_id:
          description: >-
            The contacts list this search delivers into. This is the id to read
            results with — [List the contacts in a contacts
            list](/api-reference/contacts-lists/list-contacts-list-contacts) —
            and several searches may share one.
          type: string
        created_at:
          format: date-time
          type: string
        created_by_user_email:
          description: >-
            Whatever user_email the submission carried; empty when it carried
            none.
          type: string
        id:
          description: The SEARCH's id. Pass it to the status and stop operations.
          type: string
        name:
          description: >-
            Derived at submit time from the queries and companies, e.g. 'Revenue
            leadership @ Acme, Globex and 3 others'.
          type: string
        queries:
          description: >-
            The queries as submitted, frozen. A persona edited since does not
            change them.
          items:
            $ref: '#/components/schemas/ContactSearchQueryDto'
          type:
            - array
            - 'null'
        status:
          $ref: '#/components/schemas/ContactSearchStatusDto'
      required:
        - id
        - contacts_list_id
        - name
        - created_by_user_email
        - created_at
        - company_linkedin_ids
        - queries
        - status
      type: object
    ErrorModel:
      additionalProperties: false
      properties:
        detail:
          description: >-
            A human-readable explanation specific to this occurrence of the
            problem.
          examples:
            - Property foo is required but is missing.
          type: string
        errors:
          description: Optional list of individual error details
          items:
            $ref: '#/components/schemas/ErrorDetail'
          type:
            - array
            - 'null'
        instance:
          description: >-
            A URI reference that identifies the specific occurrence of the
            problem.
          examples:
            - https://example.com/error-log/abc123
          format: uri
          type: string
        status:
          description: HTTP status code
          examples:
            - 400
          format: int64
          type: integer
        title:
          description: >-
            A short, human-readable summary of the problem type. This value
            should not change between occurrences of the error.
          examples:
            - Bad Request
          type: string
        type:
          default: about:blank
          description: A URI reference to human-readable documentation for the error.
          examples:
            - https://example.com/errors/example
          format: uri
          type: string
      type: object
    ContactSearchQueryInputDto:
      additionalProperties: false
      properties:
        canonical_job_titles_excludes_ids:
          description: Canonical job title IDs to exclude.
          items:
            type: string
          type:
            - array
            - 'null'
        canonical_job_titles_includes_ids:
          description: >-
            Canonical job title IDs to match. Prefer these over free text where
            a canonical title exists: they carry the title's translations.
          items:
            type: string
          type:
            - array
            - 'null'
        countries:
          description: >-
            Alpha-2 country codes for the PERSON's location. There is no
            sub-country filter here.
          examples:
            - - FR
          items:
            type: string
          type:
            - array
            - 'null'
        country_excludes:
          description: Alpha-2 country codes to exclude.
          examples:
            - - BE
          items:
            type: string
          type:
            - array
            - 'null'
        include_past_job_titles:
          description: >-
            Top up a query that comes back short by also searching people who
            PREVIOUSLY held one of its titles at the company. Off unless set: it
            matches people on a role they no longer hold, while the list shows
            the role they do, and it is the dominant source of off-target
            results.
          examples:
            - false
          type: boolean
        job_titles_excludes:
          description: >-
            Free-text job titles to exclude. These do exclude, unlike a NOT
            inside keywords.
          examples:
            - - Intern
          items:
            type: string
          type:
            - array
            - 'null'
        job_titles_includes:
          description: Free-text job titles to match.
          examples:
            - - Head of Sales
              - VP Sales
          items:
            type: string
          type:
            - array
            - 'null'
        keywords:
          description: >-
            Free-text search over the whole profile: headline, About, every
            role's title and description, skills, education. A boolean
            expression — uppercase AND / OR, double quotes around phrases,
            parentheses for grouping, implicit AND between bare words.


            It restricts which provider may run this query, since a provider
            with no profile-text filter would return people matching the titles
            and ignoring the words. A query whose keywords no provider can
            honour fails the whole request rather than running unfiltered.
          examples:
            - '"supply chain" AND SAP'
          type: string
        label:
          description: >-
            Short name for this query, shown beside the search in the app and
            frozen at submit time. Defaults to the persona's name, or to 'Query
            N' for an inline query.
          examples:
            - Revenue leadership
          type: string
        persona_id:
          description: >-
            One of your saved personas, supplying this query's job titles,
            countries and seniority bands. List them via [Get
            personas](/api-reference/personas/get-personas).


            The persona is read at submit time and frozen into the search, so
            editing it later does not change a search already running. Whether
            the search is widened by Tamtam's AI extension of the persona
            follows the persona's own setting, as it does in the app.


            Any field you also set below overrides the persona's value for it —
            and overriding the job titles also switches the AI extension off for
            this query, because a caller who narrowed a persona by hand must not
            have it widened again with titles they cannot see.
          examples:
            - b3c6f5e2-1234-4abc-9def-0123456789ab
          type: string
        seniority_excludes:
          description: >-
            Seniority bands to drop, same classification and same application
            point as seniority_includes.
          items:
            type: string
          type:
            - array
            - 'null'
        seniority_includes:
          description: >-
            Seniority bands to keep, read from each person's job title by
            Tamtam's own classifier and applied to the profiles the search
            fetches — never sent to a provider. A person whose title carries no
            seniority signal is kept rather than dropped.


            Allowed values: 'Owner / Partner', 'CXO', 'Vice President',
            'Director', 'Experienced Manager', 'Senior', 'Entry Level', 'In
            Training'. Because the bands apply after fetching, they narrow what
            lands in the list without narrowing what the search pays for — see
            the note on [Preview a contact
            search](/api-reference/contact-searches/preview-contact-search-count).
          examples:
            - - Director
              - CXO
          items:
            type: string
          type:
            - array
            - 'null'
      type: object
    ContactSearchQueryDto:
      additionalProperties: false
      properties:
        canonical_job_titles_excludes_ids:
          items:
            type: string
          type:
            - array
            - 'null'
        canonical_job_titles_includes_ids:
          items:
            type: string
          type:
            - array
            - 'null'
        countries:
          items:
            type: string
          type:
            - array
            - 'null'
        country_excludes:
          items:
            type: string
          type:
            - array
            - 'null'
        include_past_job_titles:
          type: boolean
        job_titles_excludes:
          items:
            type: string
          type:
            - array
            - 'null'
        job_titles_includes:
          items:
            type: string
          type:
            - array
            - 'null'
        keywords:
          type: string
        label:
          type: string
        persona_id:
          description: >-
            The persona this query was expanded from; absent for an inline
            query.
          type: string
        seniority_excludes:
          items:
            type: string
          type:
            - array
            - 'null'
        seniority_includes:
          items:
            type: string
          type:
            - array
            - 'null'
      required:
        - label
        - include_past_job_titles
      type: object
    ContactSearchStatusDto:
      additionalProperties: false
      properties:
        cancelled_jobs:
          description: >-
            Child jobs cancelled by a stop. Non-zero is what makes the phase
            Cancelled.
          format: int64
          type: integer
        companies_searched:
          description: Companies the search actually reached.
          format: int64
          type: integer
        contacts_in_list_count:
          description: >-
            Deduplicated contacts actually in the list. The number to report
            once the search is done.
          format: int64
          type: integer
        done_jobs:
          description: >-
            Child jobs in a terminal state. Poll total_jobs/done_jobs for
            progress.
          format: int64
          type: integer
        empty_reason:
          description: >-
            Why a finished search produced no contacts. Absent while it is
            running and whenever contacts were found.
          type: string
        phase:
          description: >-
            Searching while any child job is outstanding; Done once every one
            has finished; Cancelled when the search was stopped. There is no
            Failed: a search whose provider calls all failed reports Done with
            empty_reason saying so.
          enum:
            - Searching
            - Done
            - Cancelled
          type: string
        skipped_deleted_company_jobs:
          description: >-
            Child searches refused before dispatch because the company's
            LinkedIn page is gone. Never charged.
          format: int64
          type: integer
        total_jobs:
          description: Child jobs across every (query, company) pair.
          format: int64
          type: integer
        total_processed_results:
          description: >-
            Profiles processed so far, double-counting anyone found by more than
            one query. A live progress indicator, not a result count.
          format: int64
          type: integer
      required:
        - phase
        - total_jobs
        - done_jobs
        - total_processed_results
        - contacts_in_list_count
        - companies_searched
        - skipped_deleted_company_jobs
        - cancelled_jobs
      type: object
    ErrorDetail:
      additionalProperties: false
      properties:
        location:
          description: >-
            Where the error occurred, e.g. 'body.items[3].tags' or
            'path.thing-id'
          type: string
        message:
          description: Error message text
          type: string
        value:
          description: The value at the given location
      type: object
  securitySchemes:
    apikeyAuth:
      description: Account API key passed in the Authorization header
      in: header
      name: Authorization
      type: apiKey
    bearerAuth:
      description: Bearer JWT obtained via the OAuth 2.1 authorization flow
      scheme: bearer
      type: http

````