---
title: "Add companies to data source"
method: POST
path: "/api/data-sources/{sid}/add-companies"
tags: ["data_sources"]
---

# Add companies to data source

`POST /api/data-sources/{sid}/add-companies`

## Request

- Base URL: `https://beta.getsales.io/playtables/v1`
- URL: `https://beta.getsales.io/playtables/v1/api/data-sources/{sid}/add-companies`
- Auth: HTTP bearer and API key in header `Team-SID`

Append companies to a find_people source: its stored filter walks them too, no second plan. Sids already queued are skipped (result.skipped_existing); a completed, failed or paused source is re-armed and resumes at the first new company, an active one just grows its queue. At most 500 new sids per call and 500 still waiting in all (422 names how many wait); nothing new = added 0, no re-arm. When NOT: a different filter or description is a new source; any other kind is 422.

Contract:
- MCP tool `add_companies_to_data_source`, registry package `mcp.playtables/data_sources`, mount `playtables.sources`.
- Operation `action`, response envelope `action`.
- Flags: none.

## Path parameters

- `sid` string, required: The find_people source (ds_sc_) whose stored filter walks the companies; any other kind is 422. One that failed plan_failed carries no plan and plans again on its description.

## Request body

- AddCompaniesToDataSourceRequest: Request body of `add_companies_to_data_source`.
  - `company_sids` string[]: Companies to search at (cr_co_…, from the companies grid); duplicates and sids already queued are ignored. Required unless company_filter is given.
  - `company_filter` object: An account filter resolved at the call, instead of (or with) company_sids: the search_companies filter object as is (the silent accounts of this table, never a list of sids). Past 500 matches the call is 422 rule max; matches already queued are skipped.

## Response `200`

`action` success envelope.

- AddCompaniesToDataSourceResponse
  - `success` true, required
  - `operation` 'action', required
  - `action` string, required: kebab-case verb; matches the route segment.
  - `item` object, nullable, required
    - `sid` string, required
    - `team_sid` string, required
    - `playtable_sid` string, nullable, required: pt_tb_… target table, required on every create; becomes null only when the target playtable is deleted (the source auto-pauses, status_reason=playtable_deleted).
    - `name` string, required: min 1, max 255. Defaulted from the kind and its link when omitted on create ("Sales Navigator saved search", "My conversations", "AI search: CMOs in DACH").
    - `type` 'ai_search' | 'sender_search' | 'post_engagement' | 'find_people' | 'csv' | 'webhook', required: [ro] The source kind: ai_search | sender_search | post_engagement | find_people | csv | webhook. Immutable after create.
    - `object_type` 'people' | 'company', required: What promoted items become: people | company. An input of create, not a derivation of type; [ro] once the source is past draft (a csv draft's import step may still change it).
    - `sender_profile_sid` string, nullable, required: ub_sp_… the sender profile whose own LinkedIn account collects. Required for type=sender_search and post_engagement, null for every other kind - see ./sender_profiles.md.
    - `ingest_key` string, nullable, required: type=webhook only: the 40-char capability secret in the ingest URL ({api_base}/external/ingest/{ingest_key}). Stored plaintext by design so the URL stays re-viewable; rotate-ingest-key revokes it. null for every other kind.
    - `payload` union, required: Kind-discriminated config JSON, validated per kind server-side. ai_search: prompt | url + platform + surface, filters + engine (the stored plan), plan_chips / plan_warnings, next_page / latest_page (the walk cursor), collect_error (what stopped the walk: kind transient | refused, http, code, reason, message, fields; with status_reason collect_failed | collect_interrupted). sender_search: url + platform + surface, repeat, and the collector's own cursor (next_page / latest_page for the Recruiter walker, poll_since / poll_cursor / poll_max for the network and inbox pollers; the bus-served surfaces keep none). find_people: company_sids, next_index, per_company_limit, prompt, filters + engine. csv: file_path, file_type, mode, total_rows, config, match. webhook: records_path. post_engagement (2026-10-28): target + post {urn} | person {ln_nickname; ln_id, ln_member_id, name once resolved} | company {ln_nickname, ln_id, name, logo_url}, window, relevance, collect, repeat, and the walk's own state: stage (posts | judge | engage | done), posts (what it read, with verdicts and feed cursors), cycle, next_page / latest_page, migrated_from on a row born as a search with a post link before the kind existed.
      - object
        - `platform` 'linkedin' | 'sales_navigator' | 'recruiter': ai_search (link mode) and sender_search: which LinkedIn product the link opens in.
        - `surface` 'search' | 'saved_search' | 'list' | 'post' | 'my_network' | 'messenger': ai_search (link mode) and sender_search: what kind of list the link is.
        - `repeat` 'hourly' | 'daily' | 'weekly' | 'monthly', nullable: sender_search, post_engagement: the rerun interval; absent or null = runs once.
        - `mode` 'create' | 'update': csv: what the import does with its rows.
        - `target` 'post' | 'person' | 'company': post_engagement: what the source watches; the axis beside it names it (post.urn, person.ln_nickname, or company.ln_nickname with ln_id, name and logo_url once the walk resolved the page). A link is never stored: rebuild it from the axis.
        - `window` union: post_engagement, a person or a company: the window the posts are read from.
          - object
            - `days` union, required
              - …
          - object
            - `since` string, required
        - `relevance` object: post_engagement: the AI post filter; absent = every post is walked.
          - `prompt` string, required
        - `collect` object: post_engagement: the feeds the walk collects.
          - `reactions` boolean
          - `comments` boolean
          - `reposts` boolean
        - `posts` object[]: post_engagement: the posts the walk read, newest first, each with its title (the first line), text, author, published_at, counts, the filter's verdict with the reason of a skip, and the walk's cursor per feed (feeds.reactors | comments | resharers: {cursor} or {done: true}).
          - `urn` string, required
          - `title` string
          - `published_at` string, nullable
          - `verdict` 'match' | 'skip'
          - `verdict_reason` string
      - unknown[]
        - unknown
    - `field_mapping` object[], nullable, required: source→target entries: set at import for csv (and may be []), an optional override for webhook (absent = attribute auto-detect at promotion), forbidden for the three search kinds (their collector emits the canonical parser shape).
      - `source` string, required: CSV column index as a string ("3") or JSON path into the webhook body ("contact.email").
      - `target_kind` 'people_attribute' | 'company_attribute' | 'field_slug', required: people_attribute | company_attribute | field_slug.
      - `target` string, required: Attribute key (people / company) or a playtable field slug in the target table.
      - `name` string, nullable: Display name for the column a field_slug target creates (the header renamed in the mapping UI); null = the CSV header names it. Ignored for the attribute kinds and for fields that already exist.
    - `tag_sids` string[], nullable, required: cr_tg_… each - tags applied to every promoted object - see ./tags.md.
    - `qualify` object, nullable, required: The source gate, "Check on save": null = no check. The promotion reads it before a row is born (see ./data_source_items.md Lifecycle 6.5).
      - `on` boolean, required: The switch. `on` with no criterion is refused (422 on qualify.criterion, rule required_if).
      - `criterion` string, nullable, required: Who passes, in the user's words ("a decision maker at a marketing agency, not a freelancer"). Trimmed; null while the check is off.
    - `on_duplicate` 'skip' | 'move', required: Duplicate-person policy; default 'skip'.
    - `add_tags_to_duplicate` boolean, required: Merge tag_sids onto matched duplicates; default false.
    - `status` 'draft' | 'active' | 'paused' | 'completed' | 'failed', required
    - `status_reason` string, nullable, required: Reason token behind a paused or failed state (max 2048): playtable_deleted, sales_nav_required, recruiter_required, recruiter_reauth_required, executor_account_missing, plan_failed, collect_failed, collect_interrupted, post_unavailable (the post could not be read: deleted or private), target_not_found (no page at the slug), allowance_exhausted (the AI post filter ran out of credits; paused), …
    - `next_collect_at` string, nullable, required: ISO 8601 UTC - the collect clock of a search kind, advanced by checkpoints and the repeat interval. null for csv and webhook, and nulled on a bus-served sender search once its scrapes are provisioned (gtm-api owns the cadence from then on).
    - `created_by` object, required
      - `actor_type` 'user' | 'support' | 'api_key' | 'system' | 'agent', required
      - `actor_sid` string, nullable, required
      - `team_sid` string, required
      - `actor_name` string, nullable: The OAuth client that acted ("Claude", "n8n"); null for a user, an API key or a system job.
      - `oauth_client_sid` string, nullable: The acting OAuth client (id_oc_*); null off the OAuth path.
      - `reason` string, nullable: Why a system actor wrote the row (snapshot_capture_job, ...); null otherwise.
      - `permissions` object, required: Internal audit context: the grant set the actor held at write time. Not a contract.
      - `cluster_id` integer, nullable: Internal audit context: the cluster that served the write.
      - `trace_id` string, nullable: Internal audit context: the trace id of the request that wrote the row.
    - `deleted_by` object, nullable, required
      - `actor_type` 'user' | 'support' | 'api_key' | 'system' | 'agent', required
      - `actor_sid` string, nullable, required
      - `team_sid` string, required
      - `actor_name` string, nullable: The OAuth client that acted ("Claude", "n8n"); null for a user, an API key or a system job.
      - `oauth_client_sid` string, nullable: The acting OAuth client (id_oc_*); null off the OAuth path.
      - `reason` string, nullable: Why a system actor wrote the row (snapshot_capture_job, ...); null otherwise.
      - `permissions` object, required: Internal audit context: the grant set the actor held at write time. Not a contract.
      - `cluster_id` integer, nullable: Internal audit context: the cluster that served the write.
      - `trace_id` string, nullable: Internal audit context: the trace id of the request that wrote the row.
    - `created_at` string, required
    - `updated_at` string, required
    - `deleted_at` string, nullable, required
  - `result` object, required
    - `added` integer, required: Companies appended to the queue.
    - `skipped_existing` integer, required: Companies the queue already held (searched or waiting), left where they were.
    - `skipped_disqualified` integer, required: Companies left out because their account sits on the Disqualified stage: not collected again, and never a slot of the 500 cap (founder 2026-10-05; the company read answers the stage).
    - `queue_size` integer, required: payload.company_sids length after the append.
    - `pending` integer, required: Companies not searched yet, the new ones included.
  - `meta` object, required
    - `trace_id` string, required: UUID v7; same 128-bit value as the X-Trace-Id header.
    - `span_id` string, required: 16 hex chars, root span of this request.
    - `timestamp` string, required: ISO 8601 UTC (Y-m-dTH:i:sZ), response time.
    - `duration_ms` integer, required: Server-side wall clock.
    - `team_sid` string, nullable: The team this call ran in (the token team, or the team_sid override). Null when unauthenticated; absent from pre-2026-08-20 backends.
    - `actor_type` string, nullable: user | agent | api_key | system. Null when unauthenticated; absent from pre-2026-08-20 backends.

## Other responses

- `4XX`: MCP error envelope. `error.code` is one of validation_failed, nothing_to_update, not_found, relation_not_found, invalid_transition, limit_exceeded, payment_required, duplicate_rejected, conflict, delete_blocked, unauthorized, forbidden, rate_limited, not_implemented.
- `5XX`: MCP error envelope with `error.code` internal_error or service_unavailable.

---

[API](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-playtables.md) - [All operations](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-playtables/llms.txt) - [OpenAPI document](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-playtables/revisions/aa394506515d?raw)
