---
title: "Get person"
method: GET
path: "/api/people/{sid}"
tags: ["people"]
---

# Get person

`GET /api/people/{sid}`

## Request

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

One person, the person card. Includes: company, field_values, workflow_records, last_activities, notes; engagement and engagement_by_sender for the counters; signals (what they mean now).

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

## Path parameters

- `sid` string, required: The cr_pp_* sid of the row to read.

## Query parameters

- `include[]` string[]: Relations to attach to the row; omit for the bare row.

## Response `200`

`get` success envelope.

- GetPersonResponse
  - `success` true, required
  - `operation` 'get', required
  - `item` object, required
    - `sid` string, required
    - `team_sid` string, required
    - `playtable_sid` string, required: pt_tb_… - the SINGLE playtable membership; service-managed, changes only via move (create on_duplicate:move / run type move).
    - `data_source_sid` string, nullable, required
    - `data_source_item_sid` string, nullable, required
    - `company_sid` string, nullable, required
    - `context` object, nullable, required: DERIVED and read-only (CONTEXT_FIELDS_RESEARCH.md section 5): the name, first name, picture, role, headline and company a surface or a model shows next to the row, resolved at read from the observations ledger: a manual word until reset, then the newest LinkedIn-derived observation, then any other. The only place these words are: the row keeps identifiers (phase E, 2026-10-22). Null only on an event payload.
      - `name` string, nullable, required
      - `first_name` string, nullable, required: The source's own first name when it gave one, else the first word of name.
      - `picture_url` string, nullable, required: A permanent copy over a wire link, the wire link that lasts longest, an expired one counts as none.
      - `role` string, nullable, required: The title at the company (the Sales Navigator wire's position, the enrichment's).
      - `headline` string, nullable, required: The profile line (the regular and Recruiter wires, the inbox participant, the enrichment).
      - `company` object, nullable, required
        - `sid` string, nullable, required: cr_co_… when the person is linked to an account (the account's own resolved context follows); null = a name that is not an account, an import whose company column resolved to none.
        - `name` string, nullable, required
        - `domain` string, nullable, required
        - `picture_url` string, nullable, required
        - `ln_id` string, nullable, required: The company's LinkedIn id: the linked account's own, else the person's company id as a source or an edit gave it.
      - `sources` object, required: Per resolved key, where it came from and when.
    - `ln_member_id` string, nullable, required
    - `ln_id` string, nullable, required
    - `sn_id` string, nullable, required
    - `recruiter_id` string, nullable, required
    - `work_email` string, nullable, required
    - `personal_email` string, nullable, required
    - `work_phone_number` string, nullable, required
    - `personal_phone_number` string, nullable, required
    - `status` 'ok' | 'failed_validation' | 'no_contact_data' | 'duplicate' | 'stoplisted', required: Service-managed row validity (import validation, stoplist, duplicate detection) - not client-writable.
    - `duplicate_of_sid` string, nullable, required: While status is duplicate: the PRIMARY row (cr_pp_…) this one is a copy of - one primary per group, re-derived on every recheck, swapped by make_person_primary. Null otherwise.
    - `duplicate_matched_kind` 'ln_member_id' | 'ln_id' | 'sn_id' | 'recruiter_id' | 'email' | 'ln_nickname' | 'phone', nullable, required: While status is duplicate: the identifier the check matched on. Null otherwise.
    - `linkedin_status` 'need_enrichment' | 'enrichment_failed' | 'no_data' | 'blocked' | 'ok', required
    - `email_status` 'need_search' | 'no_data' | 'bounced' | 'ok' | 'catch_all' | 'unverified', nullable, required
    - `phone_status` 'need_search' | 'no_data' | 'ok', nullable, required
    - `tag_sids` string[], nullable, required
    - `external_refs` union, required: Free-form CRM sync map (hubspot_contact_id, …) - keys integration-defined, round-tripped as-is; PHP empty map serialises as [].
      - object
      - unknown[]
        - unknown
      - unknown
    - `last_enrich_at` string, nullable, required
    - `created_at` string, required
    - `updated_at` string, required
    - `deleted_at` string, nullable, required
    - `pipeline_stage_sid` string, nullable, required: The funnel stage the person is in (cr_ps_...), derived from activity or moved by hand (pipeline_stages.md); null = no stage.
  - `included` object, required
    - `company` object, nullable: CompanyDomain (N:1 via company_sid) - see ./companies.md.
      - `sid` string, required
    - `field_values` object[]: PlaytableFieldValueDomain rows - ALL live cells of the person's playtable (the table-row hydration: renders the custom columns), ordered by field position - see ./playtable_field_values.md.
      - `sid` string, required
    - `workflow_records` object[]: WorkflowRecordDomain rows in a NON-TERMINAL status only (active memberships; 1:N, limit 20, created_at DESC) - see ./workflow_records.md.
      - `sid` string, required
    - `last_activities` object[]: ActivityDomain rows (1:N, limit 20, occurred_at DESC) - the ingested timeline - see ./activities.md.
      - `sid` string, required
    - `notes` object[]: NoteDomain rows (1:N, limit 20, created_at DESC) - see ./notes.md.
      - `sid` string, required
    - `engagement` object: The person's engagement counters and dates folded over EVERY fact of the activities ledger (the search document's `engagement` keys, D43: messages sent and received per channel, opens, clicks, invitations sent / accepted / withdrawn, visits, likes, comments, endorsements, bounces, unsubscribes, replies, touches, the last touch and last message with their channel and direction). Every key present: 0 / null until a fact moves it. What the LinkedIn channel and Email channel widget cells draw; shares one read with engagement_by_sender.
    - `engagement_by_sender` object[]: The same counters per sender profile (ub_sp_ sid): the facts whose payload names it. Empty when no fact of the person names a sender profile.
      - `sender_profile_sid` string, required
    - `signals` object[]: The catalog names the person carries right now (SIGNALS_RESEARCH.md §5.3: evaluated from the engagement counters across every sender profile), each with the instant its clock started and its age in days. Empty when none holds. Ask it before drafting or deciding a next step; the thread-level names (drafts, escalations, tasks, reactions) are get_conversation's.
      - `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).
      - `since` string, nullable, required: ISO 8601 UTC: the instant the signal's clock started (our last message, the accept, the reaction...). null when the signal has no clock (draft_pending).
      - `age_days` integer, nullable, required: Whole days since `since`, at read time. null when the signal has no clock.
    - `freshness` object[]: Per read kind a watching play keeps fresh on the person (the LinkedIn profile, the company page a people-tab column reads, the posts): the play, its cadence, the last read and the next. One entry per kind at most; empty when no switched-on daily play covers the person. How old a profile read is, and whether the table re-reads it on its own.
      - `object_sid` string, required: The row: cr_pp_ on a person, cr_co_ on a company.
      - `kind` 'profile' | 'company' | 'posts', required: profile: the LinkedIn profile (a people-tab column over the person). company: the company page (a people-tab column over a company URL on a person; a companies-tab column on a company). posts: the person's posts.
      - `read_at` string, nullable, required: ISO 8601 UTC: the row's last read of this kind, whoever read (the play, a workflow's Run column step, a column's own run, a hand run). null = never read.
      - `play_sid` string, required: pt_pl_ the play whose schedule keeps this kind fresh on the row: switched on, daily, its watch names the kind, a column of the row's tab reads the kind, and the row is in its audience (a company is when one of its people is). When several plays cover the row and the kind, the shortest cadence wins.
      - `play_name` string, required: The play's name, for "re-read 2 days ago by Keep data fresh".
      - `days` integer, required: The play's cadence for this kind, in days ("every 7 days").
      - `next_at` string, nullable, required: ISO 8601 UTC: read_at plus the cadence, when the schedule reads the row again. null when never read: the next run reads it.
  - `includes` string[], required
  - `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)
