---
title: "Resolve conversation counterpart"
method: POST
path: "/api/conversations/resolve-counterpart"
tags: ["conversations"]
---

# Resolve conversation counterpart

`POST /api/conversations/resolve-counterpart`

## Request

- Base URL: `https://beta.getsales.io/unibox/v1`
- URL: `https://beta.getsales.io/unibox/v1/api/conversations/resolve-counterpart`
- Auth: HTTP bearer and API key in header `Team-SID`

Turn what a user pasted (a linkedin.com/in/ link, a Sales Navigator or Recruiter link, a bare slug or a raw URN) into the identity a thread can be born on: the canonical member id plus the other axes the answer carried, and the playtables person it links to. Call it before send_message when you only have a link.

The ladder runs cheapest first and stops at the first rung that answers: the normalizer names the one axis in the target; a URN decodes to the member id with no wire call; a slug is looked up against the team's playtables people, the sender profile's first-degree connections and the data corpus, and only then against a lite profile read, which spends one enrichment slot and can answer 429 with retry_after. result.resolution.source names the rung (free or paid).

People are never created here: a null person_sid is a normal answer, the thread stays unlinked. What a wire rung learned is written onto an existing person fill-if-empty, so the next resolve is free. No LinkedIn URL is returned or stored: the axes travel, a screen rebuilds a link from ln_nickname. 422 when the target is not a profile target, the URN will not decode, the profile stays unresolvable, or the sender profile has no LinkedIn account. A rung nobody could answer is a pause, never a verdict, and no rung behind it runs: 429 gtm_busy, 403 gtm_access_lost or gtm_scope_missing, 503 gtm_unavailable, gtm_bridge_unavailable or playtables_unavailable: try again later.

Contract:
- MCP tool `resolve_conversation_counterpart`, registry package `mcp.unibox/conversations`, mount `unibox.inbox`.
- Operation `action`, response envelope `action`.
- Flags: none.

## Request body

- ResolveConversationCounterpartRequest: Request body of `resolve_conversation_counterpart`.
  - `sender_profile_sid` string, required: The sender profile that would speak. It must have a LinkedIn account attached, else 422 sender_profile_has_no_linkedin_account; its connections and its enrichment slots are what the ladder spends.
  - `target` string, required: What the user pasted: a linkedin.com/in/ link, a Sales Navigator or Recruiter profile link, a bare vanity slug, or a raw URN. Anything the normalizer cannot read as ONE profile axis answers 422 not_a_linkedin_profile.

## Response `200`

`action` success envelope.

- ResolveConversationCounterpartResponse
  - `success` true, required
  - `operation` 'action', required
  - `action` string, required: kebab-case verb; matches the route segment.
  - `item` unknown, required
  - `result` object, required
    - `resolution` object, required: The resolved counterpart. Identity axes only.
      - `ln_member_id` string, required: The canonical LinkedIn member id, the axis a new thread is born on. Always present on a success.
      - `source` 'decoded' | 'playtables' | 'connections' | 'corpus' | 'lite_profile', required: Which rung answered: decoded (a URN was pasted, no wire call), playtables (a person already knew the slug), connections (the sender profile's first-degree connections), corpus (the gtm.service.data person corpus), lite_profile (one enrichment slot on the sender profile).
      - `ln_id` string, nullable, required: The raw LinkedIn profile URN, when the target carried it or a wire rung learned it.
      - `sn_id` string, nullable, required: The raw Sales Navigator URN, same rule.
      - `recruiter_id` string, nullable, required: The raw Recruiter URN, same rule.
      - `ln_nickname` string, nullable, required: The vanity slug, lowercased. Rebuild a profile link from it if you need to show one; no URL is returned or stored.
      - `full_name` string, nullable, required: Display name, when the rung that answered carried one (connections, corpus, lite_profile).
      - `person_sid` string, nullable, required: The playtables person this counterpart links to (cr_pp_...), or null when the team holds none. People are never created here.
  - `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-unibox.md) - [All operations](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-unibox/llms.txt) - [OpenAPI document](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-unibox/revisions/8f1bc317897f?raw)
