Analytics
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), 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.
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
}'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);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"]){
"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:
sharescounts reposts and quotes,savesisnull. - Pinterest:
viewsare video views, so an image pin reads 0.sharesisnull. - Facebook:
viewscome from the video or reel itself,savesisnull, and only reels carry watch time. - X:
viewsare 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.
curl https://api.pozy.ai/v1/posts/6650c1e2a3b4c5d6e7f80e66/authenticity \
-H "Authorization: Bearer $POZY_API_KEY"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);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()){
"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.
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 |
Open a tracking window |
GET /v1/analytics/tracking |
List tracking windows |
GET /v1/analytics/tracking/{windowId} |
Get a tracking window |
DELETE /v1/analytics/tracking/{windowId} |
Cancel a tracking window |
GET /v1/analytics/samples |
Get post samples |
GET /v1/posts/{postId}/authenticity |
Get a post's authenticity score |
GET /v1/analytics/authenticity |
List authenticity scores |
#Webhook events
| Event | When it fires |
|---|---|
post.tracking_completed |
Post tracking completed. |
post.authenticity_changed |
Post authenticity changed. |