---
title: "Activities metrics"
method: POST
path: "/api/activities/metrics"
tags: ["activities"]
---

# Activities metrics

`POST /api/activities/metrics`

## Request

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

Period-bound engagement aggregates over the activity feed. period is required (half-open [from, to) on occurred_at; occurred_at operators inside filter are ignored, created_at ones apply); group_by category | type | object_type adds per-group counts and metrics. Returns first_occurred_at, last_occurred_at and active_days_count; per-kind counts ride aggregated.counts.groups. Row-level history: search_activities.

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

## Request body

- GetActivitiesMetricsRequest: Request body of `get_activities_metrics`.
  - `filter` object: Scopes the rows the metrics are computed over: the search filter operators. Omit for all rows.
    - `sid` object: Activity sid(s), cr_ac_ prefix (prefix-validated).
      - `eq` string: Activity sid (cr_ac_…).
      - `in` string[]
    - `object_type` object: person | company.
      - `eq` 'person' | 'company'
    - `object_sid` object: Timeline scope - cr_pp_/cr_co_ sid(s); `in` for company roll-ups (person sids from ./people.md search).
      - `eq` string
      - `in` string[]
    - `category` object: Type-group filter - cheaper than a long type.in list.
      - `eq` 'object' | 'workflow' | 'ai_agent' | 'linkedin' | 'email' | 'website' | 'other'
      - `in` string[]
      - `nin` string[]
    - `type` object: Exact fact kinds.
      - `eq` 'object_created' | 'object_updated' | 'object_deleted' | 'object_playtable_changed' | 'object_pipeline_stage_changed' | 'tags_added' | 'tags_removed' | 'workflow_added' | 'workflow_restarted' | 'workflow_cancelled' | 'workflow_finished' | 'workflow_failed' | 'ai_agent_status_changed' | 'ai_agent_fact_saved' | 'linkedin_connection_request_sent' | 'linkedin_connection_request_accepted' | 'linkedin_connection_request_withdrawn' | 'linkedin_blocked' | 'linkedin_message_sent' | 'linkedin_inmail_sent' | 'linkedin_message_replied' | 'linkedin_inmail_replied' | 'linkedin_message_opened' | 'linkedin_link_clicked' | 'linkedin_post_liked' | 'linkedin_post_commented' | 'linkedin_profile_visited' | 'linkedin_skills_endorsed' | 'linkedin_invitation_received' | 'linkedin_invitation_accepted' | 'linkedin_invitation_ignored' | 'linkedin_invitation_expired' | 'linkedin_connection_removed' | 'linkedin_followed' | 'linkedin_message_reacted' | 'linkedin_message_deleted' | 'linkedin_profile_viewed_us' | 'email_sent' | 'email_replied' | 'email_opened' | 'email_bounced' | 'email_link_clicked' | 'email_unsubscribed' | 'email_complained' | 'website_visited' | 'website_key_page_visited' | 'website_form_submitted' | 'website_event' | 'manual_task_completed' | 'note_left' | 'object_enriched' | 'object_data_changed' | 'linkedin_job_changed' | 'linkedin_post_published' | 'linkedin_birthday' | 'linkedin_work_anniversary' | 'linkedin_profile_followed' | 'linkedin_post_engaged'
      - `in` string[]
      - `nin` string[]
    - `workflow_sid` object: wf_wf_ sid(s); is_null:true = facts outside any workflow.
      - `eq` string
      - `in` string[]
      - `is_null` boolean
    - `workflow_step_id` object: The step id (within its workflow) the fact was produced by; is_null:true = facts outside any step.
      - `eq` integer
      - `in` integer[]
      - `is_null` boolean
    - `occurred_at` object: ISO 8601 range; combine bounds in one object. Ignored on metrics - period owns the window.
      - `gte` string
      - `lte` string
      - `gt` string
      - `lt` string
    - `playtable_sid` object: A table's feed: the facts of its live people and the website facts of the accounts they work at. eq only.
      - `eq` string
    - `play_sid` object: What plays caused: the people their find_people sources created (object_created) and the journeys their workflow sources began or started over (workflow_added, workflow_restarted). A non-empty in only.
      - `in` string[]
    - `sender_profile_sid` object: The sender profile a fact came through (ub_sp_). A fact no sender profile made (website, CRM, workflow) never passes this leaf, leave it out to keep them; is_null true keeps only such facts. One of eq, non-empty in or is_null; another prefix is a 422.
      - `eq` string
      - `in` string[]
      - `is_null` boolean
    - `act_state` object: waiting = a task waits on the event (a draft, a question, a failed act); done = handled, nothing waiting (a task of any status names it, an act left its fact, a wait woke); none = nothing touched it, never a kind no play handles. eq or non-empty in.
      - `eq` 'waiting' | 'done' | 'none'
      - `in` string[]
    - `acted_play_sid` object: The events those plays acted on, by a task or a fact naming the event. A non-empty in only.
      - `in` string[]
    - `involved_sender_profile_sid` object: The facts stamped with those sender profiles, and the events a task or a fact of theirs answers (a post carries no sender profile; the comment drafted on it does). A non-empty in only.
      - `in` string[]
    - `involved_workflow_sid` object: One campaign's events: the events of the people who hold or held a record of those workflows (a left journey counts) and the facts those workflows wrote. Wider than workflow_sid, which keeps the workflow's own facts alone. A non-empty in only.
      - `in` string[]
    - `act_do` object: What an act did about the event, by a DONE task's act.do or the act's fact: like = linkedin_post_liked, comment = linkedin_post_commented, send_message = linkedin_message_sent; ask and add_to_workflow by the task alone. A non-empty in only.
      - `in` string[]
    - `field_sid` object: The facts a column's pull wrote, read off payload.field_sid (object_enriched, object_data_changed, linkedin_job_changed, linkedin_post_published carry it): with object_sid, one cell's history. eq or a non-empty in; a sid outside pt_fd_ is a 422.
      - `eq` string
      - `in` string[]
    - `created_at` object: ISO 8601 range on when the fact reached the ledger, which can be long after it happened (a history walk). New since I looked = created_at gt seen_at with an occurred_at floor (seen_at minus 3 days); when it happened = occurred_at. On metrics too.
      - `gte` string
      - `lte` string
      - `gt` string
      - `lt` string
  - `period` object, required: Required. Half-open [from, to) window on occurred_at - the single source of truth for the time window.
    - `from` string, required: ISO 8601 UTC window start (inclusive).
    - `to` string, required: ISO 8601 UTC window end (exclusive); must be >= from.
  - `group_by` 'category' | 'type' | 'object_type': Single axis: category | type | object_type - adds groups[] with per-value counts + metrics.

## Response `200`

`metrics` success envelope.

- GetActivitiesMetricsResponse
  - `success` true, required
  - `operation` 'metrics', required
  - `metrics` object, required
    - `filter` union: Echo of the request ActivitySearchFilter (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: Rows matching the filter (full filter scope, not the page).
        - `groups` object, required: One axis per ActivityGroupableFieldsEnum value (§4.17); PHP empty map serialises as [].
          - `category` union, required: Flat {value: count} per ActivityCategoryEnum value present in scope.
            - object
            - unknown[]
              - …
          - `type` union, required: Flat {value: count} per ActivityTypeEnum value present in scope - the engagement-widget axis.
            - object
            - unknown[]
              - …
          - `object_type` union, required: Flat {person: n, company: m} row counts.
            - object
            - unknown[]
              - …
      - `metrics` object, required
        - `first_occurred_at` string, nullable, required: ISO 8601 UTC - MIN(occurred_at) within period+filter scope; null when no facts in scope.
        - `last_occurred_at` string, nullable, required: ISO 8601 UTC - MAX(occurred_at) within period+filter scope; recency signal.
        - `active_days_count` integer, required: COUNT(DISTINCT DATE(occurred_at)) - distinct UTC days with >=1 fact; engagement spread.
    - `groups` object[]: Present when group_by was set - one entry per axis value in scope.
      - `key` string, required: Value of the group_by axis (a category / type / object_type enum member).
      - `counts` object, required
        - `total_count` integer, required: Rows matching the filter (full filter scope, not the page).
        - `groups` object, required: One axis per ActivityGroupableFieldsEnum value (§4.17); PHP empty map serialises as [].
          - `category` union, required: Flat {value: count} per ActivityCategoryEnum value present in scope.
            - object
            - unknown[]
              - …
          - `type` union, required: Flat {value: count} per ActivityTypeEnum value present in scope - the engagement-widget axis.
            - object
            - unknown[]
              - …
          - `object_type` union, required: Flat {person: n, company: m} row counts.
            - object
            - unknown[]
              - …
      - `metrics` object, required
        - `first_occurred_at` string, nullable, required: ISO 8601 UTC - MIN(occurred_at) within period+filter scope; null when no facts in scope.
        - `last_occurred_at` string, nullable, required: ISO 8601 UTC - MAX(occurred_at) within period+filter scope; recency signal.
        - `active_days_count` integer, required: COUNT(DISTINCT DATE(occurred_at)) - distinct UTC days with >=1 fact; engagement spread.
  - `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)
