---
title: "Companies metrics"
method: POST
path: "/api/companies/metrics"
tags: ["companies"]
---

# Companies metrics

`POST /api/companies/metrics`

## Request

- Base URL: `https://beta.getsales.io/playtables/v1`
- URL: `https://beta.getsales.io/playtables/v1/api/companies/metrics`
- Auth: HTTP bearer and API key in header `Team-SID`

Period-bound aggregates over a filtered company scope (reads ES - never for money decisions). aggregated.counts = "now" composition (total, with_linkedin/with_domain, groups by status / playtable_sids / data_source_sid / signals); metrics = added_count, deleted_count, linkedin/domain coverage rates. period {from,to} required, half-open; period column created_at - its filter operators are ignored. group_by → one groups[] entry per axis value (cap 500). Per-table badges ride playtables metrics.

Contract:
- MCP tool `get_companies_metrics`, registry package `mcp.playtables/companies`, mount `playtables.people`.
- Operation `metrics`, response envelope `metrics`.
- Flags: read only.

## Request body

- GetCompaniesMetricsRequest: Request body of `get_companies_metrics`.
  - `filter` object: Scopes the rows the metrics are computed over: the search filter operators. Omit for all rows.
    - `sid` object: Prefix-validated cr_co_ sid(s).
      - `eq` string: Company sid (cr_co_…).
      - `in` string[]
    - `playtable_sids` object: THE table context - every company tab passes { in: [<table>] }; array-overlap. nin answers the complement, memberless rows included.
      - `in` string[]
      - `nin` string[]
    - `pipeline_stage_category` object: The account's funnel bucket (one of seven), expanded on the server into the team's company stage sids of those categories: ask by name, never resolve the stages yourself. eq approaching = someone was invited or written to and nobody answered.
      - `eq` 'cold' | 'approaching' | 'engaging' | 'positive' | 'nurture' | 'negative' | 'disqualified'
      - `in` string[]
    - `signals` object[]: ES-only. Named signals of the account, all must hold: its own visit and the account names rolled up over the people to work (disqualified or stoplisted people do not count). Never compose the roll-ups yourself. A person's or a thread's name is a 422.
      - `key` 'awaiting_reply' | 'opened_not_replied' | 'clicked_not_replied' | 'multi_opened' | 'accepted_not_replied' | 'note_read_not_replied' | 'reacted_not_replied' | 'invited_us' | 'followed_us' | 'lead_messaged_first' | 'unanswered_inbound' | 'draft_pending' | 'escalated_open' | 'task_overdue' | 'went_quiet' | 'reactivated' | 'invite_pending' | 'connection_removed' | 'blocked_us' | 'deleted_message' | 'bounced' | 'bounced_hard' | 'complained' | 'booking_link_sent' | 'objective_reached' | 'viewed_our_profile' | 'visited_our_site' | 'visited_not_replied' | 'visited_key_page' | 'multi_visited' | 'submitted_form' | 'company_visited_our_site' | 'no_people' | 'thin_coverage' | 'nobody_reached' | 'no_engagement' | 'no_reply' | 'exhausted' | 'dormant' | 'dm_missing', required: A catalog signal name. Thread and person names: awaiting_reply (our message is the last), opened_not_replied and clicked_not_replied (our email opened or its link clicked, no reply), multi_opened (3+ opens), accepted_not_replied (invite accepted, nothing after), note_read_not_replied (the accept delivered our invite note, no reply), reacted_not_replied (an emoji on our message, no reply), invited_us (their invitation to us, unanswered), followed_us, lead_messaged_first (an inbound thread), unanswered_inbound, draft_pending (an AI draft awaits approval), escalated_open (handed to a person, task open), task_overdue, went_quiet (they had replied, then nothing), reactivated (an inbound after 30+ quiet days), invite_pending (our invite out, neither accepted nor withdrawn), connection_removed, blocked_us, deleted_message, bounced, bounced_hard (the address is gone), complained, booking_link_sent, objective_reached (the live dialog landed its goal), viewed_our_profile. Website names: visited_our_site, visited_not_replied, visited_key_page (a page the team marked key), multi_visited, submitted_form, company_visited_our_site (someone from the company, nobody recognized; a person's and a company's). Account names, companies only (dormant also a person's): no_people (nobody to work), thin_coverage (under 3 people to work), nobody_reached, no_engagement (touched, nothing came back), no_reply (touched, no reply), exhausted (everyone nurture or negative), dormant (touched once, quiet since), dm_missing (a Wrong Decision Maker named, no positive person left). Tracking facts are private: never tell the lead (submitted_form excepted).
      - `age_days` integer: At least this many days on the signal's clock (0 = any). Defaults when omitted: 1 visited_not_replied; 3 awaiting_reply and the *_not_replied names; 14 went_quiet, invite_pending; 30 no_engagement, no_reply; 180 dormant; the rest any age.
    - `data_source_sid` object: Import provenance (ds_sc_…).
      - `eq` string
      - `in` string[]
      - `is_null` boolean
    - `status` object
      - `eq` 'ok' | 'failed_validation' | 'duplicate' | 'stoplisted'
      - `ne` 'ok' | 'failed_validation' | 'duplicate' | 'stoplisted'
      - `in` string[]
      - `nin` string[]
    - `duplicate_of_sid` object: The copies of a primary: { eq: <primary sid> }. is_null:true = rows that are not flagged duplicates.
      - `eq` string: Company sid (cr_co_…).
      - `in` string[]
      - `is_null` boolean
    - `tag_sids` object: Array-overlap (any of); cr_tg_ sids - resolve labels via tags. nin answers the complement, untagged rows included.
      - `in` string[]
      - `nin` string[]
    - `ln_id` object: Exact LinkedIn company id.
      - `eq` string
      - `in` string[]
      - `is_null` boolean
    - `ln_nickname` object: Exact page slug; fuzzy matching goes through q.
      - `eq` string
      - `is_null` boolean
    - `domain` object: Lowercased apex; is_null = enrichment gap.
      - `eq` string
      - `in` string[]
      - `is_null` boolean
    - `name` object: Exact match; substring goes through q.
      - `eq` string
      - `in` string[]
      - `is_null` boolean
    - `q` string: Free text over name, domain, website, ln_nickname (ES full-text).
    - `created_at` object: ISO 8601 range; combine bounds in one object.
      - `gte` string
      - `lte` string
      - `gt` string
      - `lt` string
    - `updated_at` object
      - `gte` string
      - `lte` string
      - `gt` string
      - `lt` string
    - `deleted_at` object: Default scope is { is_null: true } (live rows).
      - `gte` string
      - `lte` string
      - `is_null` boolean
  - `period` object, required: Required. Period column: created_at - created_at operators inside filter are silently ignored.
    - `from` string, required: ISO 8601 UTC window start (inclusive).
    - `to` string, required: ISO 8601 UTC window end (exclusive); must be >= from.
  - `group_by` 'status' | 'playtable_sids' | 'data_source_sid' | 'pipeline_stage_sid': Single-axis breakdown - one groups[] entry per value. Cap 500 groups (422 on overflow - narrow the filter).

## Response `200`

`metrics` success envelope.

- GetCompaniesMetricsResponse
  - `success` true, required
  - `operation` 'metrics', required
  - `metrics` object, required
    - `filter` union: Echo of the request CompanySearchFilter (PHP empty map serializes as []).
      - object
      - unknown[]
        - unknown
    - `period` object, required
      - `from` string, required
      - `to` string, required
    - `aggregated` object, required
      - `counts` object, required
        - `total_count` integer, required: Company rows matching the filter scope.
        - `with_linkedin_count` integer, required: Rows where ln_id IS NOT NULL OR ln_nickname IS NOT NULL - multi-column OR, no group axis covers it.
        - `with_domain_count` integer, required: Rows where domain IS NOT NULL - filterable but not groupable column.
        - `groups` object, required
          - `status` union, required: Rows per CompanyStatusEnum value (3 values, bounded).
            - object
            - unknown[]
              - …
          - `playtable_sids` union, required: Rows per member table (pt_tb_… keys); a company counts once per member table.
            - object
            - unknown[]
              - …
          - `data_source_sid` union, required: Rows per provenance source (ds_sc_… keys).
            - object
            - unknown[]
              - …
          - `signals` union, required: Rows per signal name the account carries at the catalog's default threshold, present names only (a filters aggregation, not a terms axis): how many accounts of the scope read no_engagement, exhausted, dm_missing and the rest, in one page_size:0 call (ACCOUNT_PLAYS_RESEARCH.md §4.4: the chips over the Companies grid, Creator's brief).
            - object
            - unknown[]
              - …
      - `metrics` object, required
        - `added_count` integer, required: Rows with created_at within [period.from, period.to).
        - `deleted_count` integer, required: Rows soft-deleted within the period (counted with deleted scope).
        - `linkedin_coverage_rate` number, nullable, required: with_linkedin_count / total_count over the filter scope; null when total is 0.
        - `domain_coverage_rate` number, nullable, required: with_domain_count / total_count; null when total is 0.
    - `groups` object[]: Present only when group_by was set - one entry per axis value.
      - `key` string, nullable, required: Value of the group_by axis (status value / pt_tb_ / ds_sc_ sid); null = rows without a value.
      - `counts` object, required
        - `total_count` integer, required: Company rows matching the filter scope.
        - `with_linkedin_count` integer, required: Rows where ln_id IS NOT NULL OR ln_nickname IS NOT NULL - multi-column OR, no group axis covers it.
        - `with_domain_count` integer, required: Rows where domain IS NOT NULL - filterable but not groupable column.
        - `groups` object, required
          - `status` union, required: Rows per CompanyStatusEnum value (3 values, bounded).
            - object
            - unknown[]
              - …
          - `playtable_sids` union, required: Rows per member table (pt_tb_… keys); a company counts once per member table.
            - object
            - unknown[]
              - …
          - `data_source_sid` union, required: Rows per provenance source (ds_sc_… keys).
            - object
            - unknown[]
              - …
          - `signals` union, required: Rows per signal name the account carries at the catalog's default threshold, present names only (a filters aggregation, not a terms axis): how many accounts of the scope read no_engagement, exhausted, dm_missing and the rest, in one page_size:0 call (ACCOUNT_PLAYS_RESEARCH.md §4.4: the chips over the Companies grid, Creator's brief).
            - object
            - unknown[]
              - …
      - `metrics` object, required
        - `added_count` integer, required: Rows with created_at within [period.from, period.to).
        - `deleted_count` integer, required: Rows soft-deleted within the period (counted with deleted scope).
        - `linkedin_coverage_rate` number, nullable, required: with_linkedin_count / total_count over the filter scope; null when total is 0.
        - `domain_coverage_rate` number, nullable, required: with_domain_count / total_count; null when total is 0.
  - `applied_filters` object
  - `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)
