# GET /v1/social-accounts/:accountId/health (/docs/api/reference/social-accounts/health)



<Endpoint method="GET" path="/v1/social-accounts/:accountId/health" auth="Bearer" scope="social:read" phase="1" />

Returns the latest content-health snapshot for a connected social account. Layers refreshes this every 30 minutes via a Temporal cron — partners read; they don't compute.

The response is purpose-built for surfacing two things to a creator:

1. **Where am I?** — a continuous `score` (0–100) and a coarse `tier` label (`shadowbanned | new | cold | warming_up | warm | hot`).
2. **What should I do?** — a single human-readable `recommendation` string ("this is your state, this is what you should do"). Deterministic — generated from a finite template set with the account's actual numbers interpolated. No LLM, no hallucination.

Health is computed from post metrics only — view counts, comment/save/share counts, posting cadence. It does not use follower count and has no audience denominator. Engagement is judged with absolute counts against tier-relative thresholds, not ratios — so a 1M-view post with 50 comments and a 100-view post with 1 comment can both read as healthy at their respective scales.

<Parameters
  title="Path"
  rows="[
  { name: 'accountId', type: 'string', required: true, description: 'Prefixed social account id (e.g. `sa_9c1e42a0-b7f3-4e5d-a2c1-8b4f5e6c7d8e`).' },
]"
/>

## Example request [#example-request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    curl "https://api.layers.com/v1/social-accounts/sa_9c1e42a0-b7f3-4e5d-a2c1-8b4f5e6c7d8e/health" \
      -H "Authorization: Bearer lp_..."
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    const health = await layers.social.getHealth({
      accountId: "sa_9c1e42a0-b7f3-4e5d-a2c1-8b4f5e6c7d8e",
    });

    if (health.tier === "shadowbanned") {
      showBanner({
        severity: health.shadowbanSeverity,
        message: health.recommendation,
      });
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python
    health = layers.social.get_health(
        account_id="sa_9c1e42a0-b7f3-4e5d-a2c1-8b4f5e6c7d8e",
    )

    if health["tier"] == "shadowbanned":
        show_banner(severity=health["shadowbanSeverity"], message=health["recommendation"])
    ```
  </Tab>
</Tabs>

## Response [#response]

<Response status="200" description="Latest health snapshot">
  ```json
  {
    "socialAccountId": "sa_9c1e42a0-b7f3-4e5d-a2c1-8b4f5e6c7d8e",
    "score": 73,
    "tier": "warm",
    "isShadowBanned": false,
    "shadowbanSeverity": null,
    "coldStart": false,
    "managedDistribution": false,
    "engagementHealth": {
      "comments": "par",
      "saves": "above_par",
      "shares": "par"
    },
    "signals": {
      "medianRecentViews": 4200,
      "averageRecentViews": 5100,
      "totalRecentViews": 51000,
      "postsAnalyzed": 10,
      "daysSinceLastPost": 1,
      "daysOfHistory": 60,
      "postingFrequency": 0.8,
      "postingVariance": 1.2,
      "medianRecentComments": 12,
      "medianRecentSaves": 25,
      "medianRecentShares": 6
    },
    "recommendation": "You're pulling 4,200 median views with healthy engagement — breaking through. Push to 2 posts per day (not back-to-back) and double down on the formats that are working. Homogeneity is fine right now. Spend 10 minutes a day leaving substantive comments on 5–10 peer creators in your niche. We can't measure this from the platform APIs but it's a leading indicator of algorithmic lift.",
    "analyzedAt": "2026-05-07T14:30:00Z"
  }
  ```
</Response>

### Cold start vs new [#cold-start-vs-new]

The `new` tier covers two states the partner often needs to distinguish:

| State      | `tier` | `coldStart` | `signals.postsAnalyzed` | What it means                                                                            |
| ---------- | ------ | ----------- | ----------------------- | ---------------------------------------------------------------------------------------- |
| Pre-launch | `new`  | `true`      | `0`                     | Account hasn't posted yet — there is **no** signal at all. Don't render any progress UI. |
| Early run  | `new`  | `false`     | `1`–`4`                 | Posts exist but too few (\< 5) or too short a history (\< 14 days) to read meaningfully. |

Score is `0` for cold-start accounts; the recommendation tells the user (or the distribution layer, for managed accounts) to keep posting.

### Managed Social Distribution accounts [#managed-social-distribution-accounts]

When `managedDistribution: true`, the account is part of Layers' Managed Social Distribution program — content publishes via an upstream provider (DS, SSH, HA, FM, TP) with vendor-defined per-day caps. The customer cannot unilaterally change cadence on these accounts.

The recommendation language is softened accordingly:

* Customer-controlled accounts get prescriptive language: &#x2A;"Push to 2 posts per day."*
* Managed accounts get observation-style language: &#x2A;"If your distribution provider supports a higher cadence, request it."*

UI guidance: don't render "post more" or "schedule a post" controls for managed accounts — render the linked distribution-layer status instead. The `managedDistribution` flag exists exactly so partners can branch their UX without parsing the recommendation text.

### Tier ladder [#tier-ladder]

The five non-special tiers are ordered for psychological progress — each step up reads as visible improvement.

| Tier           | Median views (last 10 posts)        | What it means                                                       | Recommended cadence                                        |
| -------------- | ----------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------- |
| `shadowbanned` | Recent posts pulling 0 views        | Platform is throttling reach. See `shadowbanSeverity` for severity. | Pause posting (24h or 48h depending on severity).          |
| `new`          | \< 5 posts OR \< 14 days of history | Too early to read signal.                                           | 1 post/day for 2 weeks; we'll re-evaluate.                 |
| `cold`         | \< 100                              | We don't have enough signal yet.                                    | 1 post/day, experiment broadly with formats.               |
| `warming_up`   | 100 – 1,000                         | Some format is starting to land.                                    | 1 post/day, narrow your experiments toward what's working. |
| `warm`         | 1,000 – 10,000                      | Breaking through.                                                   | 2 posts/day (not back-to-back), double down.               |
| `hot`          | ≥ 10,000                            | You've broken through. Algorithm is lifting you.                    | 3 posts/day, expect "what app is this?" comments.          |

### Shadowban severity [#shadowban-severity]

| `shadowbanSeverity` | Trigger                                                   | Recommendation                                                                  |
| ------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `possible`          | Most-recent post has 0 views and is more than 1 hour old. | Wait about a day before posting again. Stay out of the feed.                    |
| `definite`          | ≥ 2 consecutive recent posts have 0 views.                | Pause posting and feed engagement for 48 hours. Resume with a single test post. |
| `null`              | Not shadowbanned.                                         | —                                                                               |

Shadowban detection is gated by a new-account guard — accounts with fewer than 5 posts or less than 14 days of history can't be classified as shadowbanned (small audiences look identical to throttled ones).

### Engagement health [#engagement-health]

Per-axis (comments, saves, shares), each post is bucketed against absolute thresholds for its view tier — not a ratio. The medians of those per-post buckets across the recent window become the field values.

| `tier`       | par comments | par saves | par shares |
| ------------ | ------------ | --------- | ---------- |
| `cold`       | 0            | 0         | 0          |
| `warming_up` | 1            | 1         | 0          |
| `warm`       | 10           | 20        | 5          |
| `hot`        | 50           | 100       | 30         |

A bucket value of `above_par` means the median count is at least 2× par; `par` is between par and 2× par; `below_par` is below par. When a platform doesn't expose saves on a given post (TikTok historically didn't on some surfaces), `saves` reports `par` rather than `below_par` — neutrality, not penalty.

### Score formula [#score-formula]

```
viewMomentum    = log10(medianRecentViews + 1) / log10(100_000)         # 0..1
engagementHealth = avg of {comments, saves, shares} bucket scores       # 0..1
consistency     = blend of postingFrequency and daysSinceLastPost       # 0..1

score = round(100 × (0.5·viewMomentum + 0.3·engagementHealth + 0.2·consistency))
score -= 30 if shadowbanned
score = clamp(score, 0, 100)
```

The score is informational — `tier` and `recommendation` are what to render in your UI. Score is most useful for ranking accounts within a portfolio or trending an account over time.

## Recommendation [#recommendation]

`recommendation` is a single string, deterministic, and personalised by interpolating the account's actual numbers. It always covers two layers:

1. **Primary line** — state + cadence action ("you're at X median views — do Y").
2. **Optional appendices** — flagged when the data warrants it: spotty distribution (high `postingVariance`), and a peer-engagement nudge (suppressed when shadowbanned/new).

Render it as one block of text. It's safe to show verbatim.

## When the snapshot doesn't exist [#when-the-snapshot-doesnt-exist]

Returns `404` if the account hasn't been analyzed yet — typically the case for the first \~30 minutes after the Account Health Monitor system layer was provisioned for the account. Poll back, or call again after the first scheduled run.

## Errors [#errors]

| Status | Code              | When                                                             |
| ------ | ----------------- | ---------------------------------------------------------------- |
| 401    | `UNAUTHENTICATED` | Missing or invalid key.                                          |
| 403    | `FORBIDDEN_SCOPE` | Key lacks `social:read`.                                         |
| 404    | `NOT_FOUND`       | Account not in your organization, or no analysis row exists yet. |
| 429    | `RATE_LIMITED`    | Read budget exhausted.                                           |

## See also [#see-also]

* [`GET /v1/projects/:projectId/social-accounts`](/docs/api/reference/social-accounts/list-social-accounts) — list accounts to find their `socialAccountId`.
* [`POST /v1/projects/:projectId/social/reauth-url`](/docs/api/reference/social-accounts/reauth-url) — fix a `reauth_required` account before its health can recover.
