*Social Media Manual — [index](../MANUAL.md)*

# 2. Identity and members

A **member** is the social-side identity of whoever is calling. Accounts, drafts, media,
schedules and authorizations all hang off a member. Understanding where the member comes from
explains almost every surprise in this area.

## The principal comes from the bearer token

The caller's identity is taken from the MCP bearer token and nothing else.

| token | principal | typical caller |
|---|---|---|
| OAuth grant obtained through the server's consent page | `key:<api_key_id>` | an assistant connected by a user |
| first-party API key | `user:<id>` | DataHandler staff tooling |

The consent page uses open consent: every grant is bound to one shared service user, so the
grant's own api key is what distinguishes one person from another. The first social call a
principal makes creates its member row.

Consequences:

- **A reconnected connector is a new person.** If the user removes the connector from their
  assistant and adds it again, a new grant is issued, the principal is a new `key:` and the
  social member is new. Their accounts, drafts and schedules still exist under the old member,
  but the new session cannot see them.
- **Nothing is ever joined by name or email.** The agent cannot say "I am Jesse" and get Jesse's
  accounts. There are exactly two ways to reach an existing member from a fresh session:
  an invitation from the owner of an account (chapter 3), or a recovery code the user minted
  earlier.

## Recovery codes

A recovery code lets a user carry their social identity across a reconnection.

1. While the old session is still working, call `social_create_recovery_code`. The reply has
   `recovery_code` (prefix `srec_`), shown once, valid 72 hours. Tell the user to keep it.
2. In the fresh session, call `social_claim_recovery_code` with the code. The new principal is
   bound to the old member: `member_id`, `accounts` (how many they get back), `already_bound`.

Rules:

- A session that already **owns** accounts cannot claim a code (`RECOVERY_CONFLICT`). Claim
  before connecting anything.
- The old session keeps its access until its own grant is revoked; claiming does not log it
  out.
- A bad, used or expired code is `RECOVERY_INVALID`. Mint a new one from the old session if it
  still exists; otherwise the owner of each account must invite the new session.

Suggest a recovery code proactively when a user has just connected an account: it is the only
cheap insurance against the "new connector, new person" rule.

## What `social_get_status` tells you about identity

| field | meaning |
|---|---|
| `enabled` | the feature is on for this server |
| `member` | `null` until the caller has made a member-creating call; then `{member_id, principal, display_name, created_at}` |
| `accounts` | how many accounts the caller can use in any role |

`member: null` with `accounts: 0` is a brand-new identity. Do not treat that as "the user has
no social accounts"; it may mean they reconnected the connector and need to claim a recovery
code or an invitation.

## Anonymous callers

A call with no authenticated principal answers `IDENTITY_REQUIRED`. The fix is on the user's
side: connect the assistant through the server's consent page so the bearer token carries an
api key.
