# Tracking and authenticity

Watch a post's numbers for a set number of days, read every verified sample, and score whether its views look organic.

Regular analytics give you the latest numbers for every post. A **tracking window** goes further for the posts that matter: Pozy watches one post for a fixed period, keeps every reading it takes, does a final read when the period ends and tells you by webhook. On top of those readings it computes an **authenticity score** that flags views that do not look organic.

Typical uses are paying creators on verified views (see [Creator campaigns](https://pozy.ai/docs/creators)), proving campaign results to a client, and spotting bought views before you reshare a post.

All numbers come from the platform through Pozy's own sync. Nothing is self-reported by the poster.

## Open a tracking window

Pass the Pozy `postId`, or an `accountId` plus the post's public `url` for a post published outside Pozy.

```bash cURL
curl -X POST https://api.pozy.ai/v1/analytics/tracking \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "6650c1e2a3b4c5d6e7f80a22",
    "url": "https://www.tiktok.com/@acme/video/7431001122334455667",
    "trackingDays": 30
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/analytics/tracking", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    accountId: "6650c1e2a3b4c5d6e7f80a22",
    url: "https://www.tiktok.com/@acme/video/7431001122334455667",
    trackingDays: 30,
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.window.id, data.window.endsAt);
```
```python Python
import os
import requests

resp = requests.post(
    "https://api.pozy.ai/v1/analytics/tracking",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={
        "accountId": "6650c1e2a3b4c5d6e7f80a22",
        "url": "https://www.tiktok.com/@acme/video/7431001122334455667",
        "trackingDays": 30,
    },
    timeout=30,
)
print(resp.json()["window"])
```

```json
{
  "window": {
    "id": "6650c1e2a3b4c5d6e7f81001",
    "postId": "6650c1e2a3b4c5d6e7f80e66",
    "accountId": "6650c1e2a3b4c5d6e7f80a22",
    "platform": "tiktok",
    "platformPostId": "7431001122334455667",
    "startsAt": "2026-10-30T09:00:00.000Z",
    "endsAt": "2026-11-29T09:00:00.000Z",
    "status": "open",
    "finalReadAt": null,
    "completionReason": null,
    "requestedBy": "api",
    "latest": null
  }
}
```

`trackingDays` defaults to 30 and goes up to 90. Opening a window on a post that already has one returns the existing window instead of a second one, so the call is safe to repeat.

## Follow the window

| Status | Meaning |
| --- | --- |
| `open` | Collecting samples on every analytics sync |
| `completing` | The period ended and the final read is running |
| `completed` | Done. `completionReason` says how it ended |
| `cancelled` | You cancelled it. There is no final read and no webhook |

| `completionReason` | Meaning |
| --- | --- |
| `final_read` | The final read succeeded. `latest` holds the closing numbers |
| `unreachable` | The sync failed or no sample was ever taken |
| `post_deleted` | The post disappeared from the platform |
| `cancelled` | Cancelled with `DELETE /v1/analytics/tracking/{windowId}` |

`latest` on a window holds the newest verified numbers: `views`, `likes`, `comments`, `shares`, `saves` and the time of that reading. List windows with `GET /v1/analytics/tracking`, filtered by `accountId` and `status`.

When the window ends, Pozy sends **`post.tracking_completed`** with the final numbers. Subscribe to it instead of polling the window.

## Read the samples

`GET /v1/analytics/samples?postId=...` returns every reading for a post, oldest first, one per analytics sync. Each sample holds the running totals the platform reported at that moment.

| Field | Notes |
| --- | --- |
| `syncedAt` | When the reading was taken |
| `views`, `likes`, `comments` | Running totals, including anything from before the window opened |
| `shares`, `saves` | `null` (not zero) on platforms without that counter |
| `watchSeconds`, `completionRate` | `null` when the platform gives no watch signal |

Platform specifics worth knowing:

- **Threads:** `shares` counts reposts and quotes, `saves` is `null`.
- **Pinterest:** `views` are video views, so an image pin reads 0. `shares` is `null`.
- **Facebook:** `views` come from the video or reel itself, `saves` is `null`, and only reels carry watch time.
- **X:** `views` are impressions, because X's video view count is shared by every post that carries the same video.

Pass `fromDate`, `toDate` and `limit` (up to 2,000) to page through long histories.

## Authenticity score

`GET /v1/posts/{postId}/authenticity` returns a score from 0 to 100 computed from the verified samples. Higher looks more organic.

```bash cURL
curl https://api.pozy.ai/v1/posts/6650c1e2a3b4c5d6e7f80e66/authenticity \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/posts/6650c1e2a3b4c5d6e7f80e66/authenticity", {
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.score, data.confidence, data.reasons);
```
```python Python
resp = requests.get(
    "https://api.pozy.ai/v1/posts/6650c1e2a3b4c5d6e7f80e66/authenticity",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    timeout=30,
)
print(resp.json())
```

```json
{
  "postId": "6650c1e2a3b4c5d6e7f80e66",
  "score": 62,
  "confidence": 0.8,
  "reasons": [{ "code": "late_block", "weight": 35 }],
  "version": "2026-10-29.1",
  "computedAt": "2026-10-30T03:00:00.000Z",
  "sampleCount": 14
}
```

Every reason subtracts its `weight` in points from 100:

| Code | What it means |
| --- | --- |
| `late_block` | One large jump in views long after publishing, instead of gradual growth |
| `flat_engagement` | Likes, comments and other counters are low for the number of views |
| `view_follower_gap` | Views far above the follower count, with no shares to explain them |
| `cap_seeking` | Views repeatedly land just under a payout cap |
| `prior_flags` | Earlier posts from the same account were flagged |
| `no_watch_signal` | No watch time or completion data. Weight 0; it only lowers `confidence` |

`confidence` (0 to 1) tells you how much data backs the score: few samples or no watch data means low confidence. `version` names the scoring model, so you can tell when a score was computed under an older one.

Scores younger than 30 minutes are served as stored; older ones are recomputed on read. To review an account's posts in bulk, call `GET /v1/analytics/authenticity?accountId=...` with `minScore` or `maxScore` (for example `maxScore=40` for the suspicious ones), plus `from`, `to`, `limit` and `skip`.

> **When the webhook fires:**
> 
> `post.authenticity_changed` fires when a tracked post's score crosses 40 in either direction while its window is open. It never fires for a post's first score. The payload carries the new and previous score, the confidence and the reasons.

## Treat the score as a signal

The score is evidence, not a verdict. Use it to hold a payout for review or to ask a creator a question, and read the `reasons` before you act. A small account with a genuinely viral post can trip `view_follower_gap` on day one.

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`POST /v1/analytics/tracking`](https://pozy.ai/docs/api/openTrackingWindow) | Open a tracking window |
| [`GET /v1/analytics/tracking`](https://pozy.ai/docs/api/listTrackingWindows) | List tracking windows |
| [`GET /v1/analytics/tracking/{windowId}`](https://pozy.ai/docs/api/getTrackingWindow) | Get a tracking window |
| [`DELETE /v1/analytics/tracking/{windowId}`](https://pozy.ai/docs/api/cancelTrackingWindow) | Cancel a tracking window |
| [`GET /v1/analytics/samples`](https://pozy.ai/docs/api/getPostSamples) | Get post samples |
| [`GET /v1/posts/{postId}/authenticity`](https://pozy.ai/docs/api/getPostAuthenticity) | Get a post's authenticity score |
| [`GET /v1/analytics/authenticity`](https://pozy.ai/docs/api/listPostAuthenticity) | List authenticity scores |

## Webhook events

| Event | When it fires |
| --- | --- |
| [`post.tracking_completed`](https://pozy.ai/docs/webhooks/events#post-tracking-completed) | Post tracking completed. |
| [`post.authenticity_changed`](https://pozy.ai/docs/webhooks/events#post-authenticity-changed) | Post authenticity changed. |
