---
title: "Playtables metrics"
method: POST
path: "/api/playtables/metrics"
tags: ["playtables"]
---

# Playtables metrics

`POST /api/playtables/metrics`

## Request

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

Per-table child-object aggregates (reads Elasticsearch - listing discipline; never use for money decisions). Metrics: people_count, companies_count, people_with_linkedin_count are "now" totals; people_added_count, companies_added_count are bounded by the required period (half-open [from,to) over the children's created_at - attach time proxied by creation). filter scopes which TABLES aggregate (default live rows).

Two grouping modes, mutually exclusive (both set → 422): group_by:"sid" → groups[] with one {key, counts, metrics} entry per playtable (dashboard per-table numbers; cap 500 groups - narrow filter.sid.in); group_by_field_sid:"pt_fd_…" → field_value_groups[]: distribution of the field's object_type rows across the options of one scalar_select field (stage funnel; null key = empty/unset cell). group_by_field_sid requires filter.sid.eq = the field's playtable and a scalar_select field (422 otherwise).

When NOT: per-status people breakdown inside one table → search_people counts with filter.playtable_sid; listing/editing rows → people / companies tools.

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

## Request body

- GetPlaytablesMetricsRequest: Request body of `get_playtables_metrics`.
  - `filter` object: Scopes the rows the metrics are computed over: the search filter operators. Omit for all rows.
    - `sid` object: Prefix-validated pt_tb_ sid(s).
      - `eq` string
      - `in` string[]
    - `name` object: Exact match (dedup checks); substring matching goes through q.
      - `eq` string
      - `in` string[]
    - `agent_sid` object: The AI SDR the table picked (ub_ag_…); is_null:true = the tables on the team's default agent.
      - `eq` string
      - `in` string[]
      - `is_null` boolean
    - `q` string: Full-text LIKE (prefix/infix) over name + description.
    - `created_at` object: ISO 8601 range; combine bounds in one 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. Bounds only the *_added_count metrics; *_count totals are "now".
    - `from` string, required: ISO 8601 UTC window start (inclusive).
    - `to` string, required: ISO 8601 UTC window end (exclusive); must be >= from.
  - `group_by` 'sid': Per-table breakdown - one groups[] entry per playtable. Cap 500 groups. Mutually exclusive with group_by_field_sid.
  - `group_by_field_sid` string: scalar_select field sid - option distribution (field_value_groups[]). Requires filter.sid.eq = the field's playtable. Mutually exclusive with group_by.

## Response `200`

`metrics` success envelope.

- GetPlaytablesMetricsResponse
  - `success` true, required
  - `operation` 'metrics', required
  - `metrics` object, required
    - `filter` union: Echo of the request PlaytableSearchFilter (PHP empty map serialises as []).
      - object
      - unknown[]
        - unknown
    - `period` object, required
      - `from` string, required
      - `to` string, required
    - `aggregated` object, required
      - `counts` object, required
        - `total_count` integer, required: Playtable rows matching the filter scope.
        - `groups` union, required: Always empty - no group axes ('sid' is metrics-only). PHP empty map serialises as [].
          - object
          - unknown[]
            - unknown
      - `metrics` object, required
        - `people_count` integer, required: People whose playtable_sid is in the filter scope (now).
        - `companies_count` integer, required: Companies whose playtable_sids[] intersects the scope (now, ES).
        - `people_added_count` integer, required: People created within [period.from, period.to).
        - `companies_added_count` integer, required: Companies created within the period (attach time is not stored; creation is the proxy - legacy semantics).
        - `people_with_linkedin_count` integer, required: People with a LinkedIn identifier present (now); legacy have_linkedin.
    - `groups` object[]: Present when group_by:'sid' - one entry per playtable.
      - `key` string, required: Playtable sid (pt_tb_…) - the group_by:'sid' axis.
      - `counts` object, required
        - `total_count` integer, required: Playtable rows matching the filter scope.
        - `groups` union, required: Always empty - no group axes ('sid' is metrics-only). PHP empty map serialises as [].
          - object
          - unknown[]
            - unknown
      - `metrics` object, required
        - `people_count` integer, required: People whose playtable_sid is in the filter scope (now).
        - `companies_count` integer, required: Companies whose playtable_sids[] intersects the scope (now, ES).
        - `people_added_count` integer, required: People created within [period.from, period.to).
        - `companies_added_count` integer, required: Companies created within the period (attach time is not stored; creation is the proxy - legacy semantics).
        - `people_with_linkedin_count` integer, required: People with a LinkedIn identifier present (now); legacy have_linkedin.
    - `field_value_groups` object[]: Present when group_by_field_sid is set.
      - `key` string, nullable, required: Select option value; null = empty/unset cell.
      - `objects_count` integer, required: Objects of the field's object_type carrying this value.
  - `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)
