01 Contract
Every request requires Authorization: Bearer <API_KEY>. Replace {platform} with tiktok or instagram. Your client must have explicit positive social read and refresh limits.
Profile and post reads return the latest stored observation, not a series — see History below for the chartable series endpoint. A stale object still returns 200 with freshness.status: "stale". Missing collection evidence returns 404 NOT_COLLECTED.
The provider-parity extension remains gated. Profile/post resolver mechanics are implemented but return 422 FEATURE_UNAVAILABLE before quota or database access for both providers. Resolver rollout requires separately sanctioned public/tombstone evidence plus current handle/shortcode alias evidence; this release does not authorize a legacy alias backfill or infer public access from authenticated observations. No new provider enrichment fields or Instagram single-post sampler were enabled, and profile-post cursors still page stored rows only — there is no upstream continuation cursor or measured cap. Comment acquisition is unavailable. During the generation-aware comment migration, entitled clients receive 503 FEATURE_UNAVAILABLE before quota from comment GET and refresh in maintenance; the target production mode is stored_read_only, where entitled TikTok stored GETs work and refresh returns 422 FEATURE_UNAVAILABLE before quota. live acquisition remains forbidden pending separate sanctioned evidence.
Null is not zero. A metric is null when it was not observed; 0 means it was observed as zero. Profile and post data becomes stale after 30 hours.
02 Read a profile
curl -s "https://api.metrix.ac/api/v1/social/tiktok/profiles/alice" \
-H "Authorization: Bearer $TM_API_KEY"
{
"id": "7123", "platform": "tiktok", "url": "https://www.tiktok.com/@alice",
"username": "alice", "display_name": null, "bio": null, "verified": null,
"follower_count": 120400, "following_count": null, "video_count": 82,
"observed_at": "2026-07-22T14:10:00Z",
"freshness": { "status": "fresh", "age_seconds": 420, "stale_after_seconds": 108000 },
"data_source": null, "last_refresh_at": null
}
Every accepted field is always present. A field with no real observation is null — never removed, inferred, or zeroed. last_refresh_at is caller-relative: your most recent accepted refresh job for this profile, not observation success.
curl -sG "https://api.metrix.ac/api/v1/social/instagram/resolve/profile" \
--data-urlencode "ref=https://www.instagram.com/alice/" \
-H "Authorization: Bearer $TM_API_KEY"
Currently returns 422 FEATURE_UNAVAILABLE before quota or database access. The database-only implementation accepts handles, immutable profile IDs, and canonical Instagram/TikTok profile URLs, but rollout is hardcoded off until sanctioned public/tombstone and current-alias evidence is accepted. Authenticated observations are not public evidence, and no legacy handle backfill is authorized.
03 Read posts
limit 1–50; default 30.curl -s "https://api.metrix.ac/api/v1/social/instagram/profiles/alice/posts?limit=30&cursor={cursor}" \
-H "Authorization: Bearer $TM_API_KEY"
Continue with the opaque next_cursor only on the same platform and profile. Ordering is stable by publication time and post ID. Historical rows without a publication time sort last. This route preserves the frozen pre-provider-parity SocialPostList JSON and OpenAPI contract.
curl -s "https://api.metrix.ac/api/v1/social/tiktok/posts/741234567890" \
-H "Authorization: Bearer $TM_API_KEY"
This route preserves the frozen pre-provider-parity SocialPost JSON and OpenAPI contract. Private or deleted stored posts return PRIVATE_OR_DELETED without exposing upstream or collector details.
curl -sG "https://api.metrix.ac/api/v1/social/instagram/resolve/post" \
--data-urlencode "ref=https://www.instagram.com/reel/AbC_123/" \
-H "Authorization: Bearer $TM_API_KEY"
Currently returns 422 FEATURE_UNAVAILABLE before quota or database access. The database-only implementation accepts immutable IDs, Instagram shortcodes, and canonical Instagram/TikTok post URLs, but rollout is hardcoded off until sanctioned public/tombstone and current-alias evidence is accepted. No legacy shortcode backfill is authorized.
curl -s "https://api.metrix.ac/api/v1/social/provider-parity/instagram/profiles/alice/posts?limit=30&cursor={cursor}" \
-H "Authorization: Bearer $TM_API_KEY"
Returns the provider-parity post fields plus stored-only coverage. next_cursor/has_more page through stored rows only. coverage.scope remains stored_window, with continuation_available=false and stop_reason="unsupported"; it never claims upstream exhaustion.
curl -s "https://api.metrix.ac/api/v1/social/provider-parity/tiktok/posts/741234567890" \
-H "Authorization: Bearer $TM_API_KEY"
Projects stored-only shortcode, creator display/verification facts, hashtags, partial media, sound, and save-count facts. null, [], and 0 retain distinct meanings.
days 1–30, default 30.curl -s "https://api.metrix.ac/api/v1/social/tiktok/profiles/alice/history?days=30" \
-H "Authorization: Bearer $TM_API_KEY"
Both TikTok and Instagram return a chartable snapshot series (history_available: true) over the stored 30-day retention window — Instagram now has a real creator-snapshot writer (shipped 2026-07-29). Each point carries the observed follower_count and, for Instagram, per-batch average like_count/comment_count. An unknown profile on either platform returns 404 NOT_COLLECTED.
7d, 30d.curl -s "https://api.metrix.ac/api/v1/social/instagram/profiles/alice/velocity" \
-H "Authorization: Bearer $TM_API_KEY"
{
"profile_id": "ig-123", "platform": "instagram",
"follower_growth": {
"7d": null,
"30d": { "abs_change": 1200.0, "pct_change": 8.7, "per_day": 40.0, "n_points": 5 }
},
"engagement": {
"post_engagement_rate": {
"7d": null,
"30d": { "abs_change": 0.004, "pct_change": 6.1, "per_day": 0.0001, "n_points": 5 }
}
},
"data_through": "2026-07-29T14:00:00Z"
}
Follower growth + post-engagement-rate growth for one Instagram creator, computed on read from the same snapshot series /history returns — no new writer. Post-engagement rate is (avg like + avg comment) / follower_count per snapshot. Windows are 7d and 30d only (Instagram crawls ~daily, so a 24h window would rarely hold the two points growth needs); a window is null until at least two snapshots fall inside it. data_through is the newest snapshot timestamp. An uncollected handle returns 404 NOT_COLLECTED. TikTok creators use /api/v1/creators/{creator_id}/velocity instead.
limit 1–50; default 30. Cursor-paginated.curl -s "https://api.metrix.ac/api/v1/social/tiktok/posts/741234567890/history?limit=30&cursor={cursor}" \
-H "Authorization: Bearer $TM_API_KEY"
Metric snapshots for one post, newest first, over the same stored corpus profile/post reads use — no new writer. A known Instagram post returns the same shape with history_available: false and an empty points list. An unknown post on either platform returns 404 NOT_COLLECTED; an exhausted or malformed cursor returns 422 INVALID_CURSOR.
For an entitled client in maintenance, both stored comment GETs return 503 FEATURE_UNAVAILABLE before read quota or comment-table access. In stored_read_only, entitled TikTok stored GETs are available while every refresh remains unavailable before quota. Instagram comment GETs remain unavailable in every mode, and live acquisition is not an enabled client contract.
limit 1–50; default 20. Cursor-paginated.curl -s "https://api.metrix.ac/api/v1/social/tiktok/posts/741234567890/comments?limit=20" \
-H "Authorization: Bearer $TM_API_KEY"
The stored bounded top-level comment snapshot (up to 60). TikTok requires the per-client social_comments_enabled entitlement; Instagram has an independent default-off social_instagram_comments_enabled grant and never inherits TikTok access. Without the requested platform's grant, the route returns 403 FEATURE_UNAVAILABLE before capability, reference, or quota work (the admin key grants no implicit access). After entitlement passes, maintenance returns 503 FEATURE_UNAVAILABLE before quota or comment DB access. Never collected → 404 NOT_COLLECTED; a stored collection classified empty → 200 with items: []. Current upstream acquisition has not proved a legitimate empty/private/deleted classifier and never converts an ambiguous empty response into stored empty. The default envelope preserves coverage: "bounded" and the boolean upstream_has_more. Request expand=collection_coverage to add rich stored-window state; the field is otherwise absent. It never claims upstream completeness or treats the stored count as the post total. Any future staging-authorized latest replacement starts a new local generation, so cursors from the replaced snapshot return 409 CURSOR_EXPIRED; malformed and cross-target cursors remain 422 INVALID_CURSOR. An Instagram-entitled client still receives 422 FEATURE_UNAVAILABLE from the disabled capability.
curl -s "https://api.metrix.ac/api/v1/social/tiktok/comments/{comment_id}/replies?limit=20" \
-H "Authorization: Bearer $TM_API_KEY"
The stored bounded direct-reply snapshot for one top-level comment. Reply collection is deferred — TikTok exposes no comment ID in the page DOM, so a specific parent's replies can't be deterministically fetched. Refresh fails before quota, while this GET remains stored-only and returns 404 NOT_COLLECTED until a snapshot exists. Maintenance returns 503 FEATURE_UNAVAILABLE before quota or parent/comment lookup. Stored responses use the same legacy bounded coverage and generation-scoped cursor behavior as top-level comments; expand=collection_coverage opts into rich state. Requires social_comments_enabled.
04 Track a profile
curl -s -X POST "https://api.metrix.ac/api/v1/social/instagram/profiles/alice/track" \
-H "Authorization: Bearer $TM_API_KEY"
{ "platform": "instagram", "username": "alice", "tracked": true,
"initial_refresh_status": "accepted",
"refresh": { "job_id": 42, "state": "pending", "deduplicated": false,
"poll_url": "/api/v1/jobs/42" } }
Tracking is client-owned — there is no model_id or relationship. A stale or never-seen profile gets an accepted refresh job (initial_refresh_status: "accepted"). An already-fresh stored profile returns "not_needed" and refresh: null — no refresh job is enqueued. Tracking the same profile again returns 409 PROFILE_ALREADY_TRACKED and enqueues nothing. If the optional refresh attempt itself hits a quota or queue failure, the track still succeeds: 200, tracked: true, initial_refresh_status is "quota_exceeded" or "enqueue_failed", and refresh: null — a failed refresh attempt never rolls back the membership.
curl -s -X DELETE "https://api.metrix.ac/api/v1/social/tiktok/profiles/alice/track" \
-H "Authorization: Bearer $TM_API_KEY"
Hard-deletes only this client's tracking row. The stored profile, posts, and snapshots are shared data and are never touched by an untrack. A profile you don't track returns 404 NOT_TRACKED; success returns 204.
05 Request a refresh
curl -s -X POST "https://api.metrix.ac/api/v1/social/tiktok/profiles/alice/refresh" \
-H "Authorization: Bearer $TM_API_KEY"
{ "job_id": 42, "state": "pending", "deduplicated": false,
"poll_url": "/api/v1/jobs/42" }
Poll GET /api/v1/jobs/42. Repeating a request while the same client refresh is active returns that job with deduplicated: true and consumes no additional refresh unit.
curl -s -X POST "https://api.metrix.ac/api/v1/social/tiktok/posts/741234567890/refresh" \
-H "Authorization: Bearer $TM_API_KEY"
{ "job_id": 108, "state": "pending", "deduplicated": false,
"poll_url": "/api/v1/jobs/108" }
The post must already be collected — an uncollected post returns 404 NOT_COLLECTED. A repeat call while a refresh for this post is already active returns that same job with deduplicated: true and consumes no additional refresh unit. Instagram post refresh returns 503 FEATURE_UNAVAILABLE: no Instagram single-post sampler exists yet.
curl -s -X POST "https://api.metrix.ac/api/v1/social/tiktok/posts/741234567890/comments/refresh" \
-H "Authorization: Bearer $TM_API_KEY" \
-H "Content-Type: application/json" -d '{"parent_comment_id": null, "mode": "latest"}'
{ "error": "FEATURE_UNAVAILABLE",
"message": "Top-level comment refresh awaits sanctioned staging verification.",
"request_id": "..." }
In stored_read_only, the safe default returns 422 FEATURE_UNAVAILABLE before post lookup, quota reservation, or queue access; maintenance returns 503 FEATURE_UNAVAILABLE at the same pre-quota boundary. The prior live smoke mapped populated fields but did not prove exact live key sets or a legitimate empty/private/deleted case. The public latest operation is also not implementation-ready until claim, job, and quota share one target-global transaction and the worker rechecks ownership before publication. mode still defaults to "latest"; disabled mode: "continue" also returns 422 FEATURE_UNAVAILABLE before reference resolution or quota. NO_CONTINUATION is reserved for a future supported capability without usable state. Clients cannot submit a generation, upstream cursor, page count, proxy, or retry setting. A non-null parent_comment_id — a reply request — returns 422 FEATURE_UNAVAILABLE before quota outside maintenance. Instagram also remains unavailable.
TikTok stored comment access requires social_comments_enabled; Instagram requires the independent default-off social_instagram_comments_enabled. Neither is implied by quota, tier, admin status, or the other provider's grant. The dormant top-level reader retains the existing bound of 60 objects across 3 pages; that is a legacy product bound, not a newly measured cap. Upstream continuation is unsupported, Instagram comments remain unavailable pending live field proof and implementation, and TikTok reply refresh is deferred because the DOM exposes no deterministic parent comment ID.
06 Monitor a video
Creating or updating recurring TikTok video sampling requires the pro tier (or equivalent — growth also qualifies) in addition to explicit positive social read and refresh limits. Without it, monitor returns 403 TIER_REQUIRED. Unmonitor remains available after a downgrade so a client can stop existing work. Instagram video monitoring is not available.
interval_seconds 1800+; default 21600 (6h).curl -s -X POST "https://api.metrix.ac/api/v1/social/tiktok/posts/741234567890/monitor?interval_seconds=21600" \
-H "Authorization: Bearer $TM_API_KEY"
{ "platform": "tiktok", "post_id": "741234567890",
"monitored": true, "refresh_interval_seconds": 21600 }
The post must already be collected — an uncollected post returns 404 NOT_COLLECTED, the same as refresh. Monitoring itself is free — no job, no quota unit. A repeat call updates the refresh interval. A scheduled sample is enqueued on your behalf once the interval elapses; a manual POST .../refresh shares the same active job so the two paths never double-charge.
curl -s -X DELETE "https://api.metrix.ac/api/v1/social/tiktok/posts/741234567890/monitor" \
-H "Authorization: Bearer $TM_API_KEY"
Hard-deletes only the monitoring membership. Stored snapshots and the post itself are preserved. A post you don't monitor returns 404 NOT_TRACKED.
07 Errors, quotas, and correlation
Responses include X-Request-ID. Social error bodies repeat it as request_id; include that value when reporting a problem. Errors never contain raw provider, proxy, cookie, SQL, or collector text.
{ "error": "NOT_COLLECTED",
"message": "No stored instagram profile for @alice. Request a refresh first.",
"request_id": "4a9fb779-39b5-4d60-b218-610d742ac9ca" }
Quota responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix time). Windows reset at 00:00 UTC. Reads and newly accepted refreshes use separate budgets.
Domain codes: INVALID_PLATFORM_OR_ID, INVALID_REQUEST, INVALID_CURSOR, CURSOR_EXPIRED, NO_CONTINUATION, NOT_COLLECTED, NOT_TRACKED, FEATURE_UNAVAILABLE, PRIVATE_OR_DELETED, RESOURCE_UNAVAILABLE, PROFILE_ALREADY_TRACKED, TIER_REQUIRED, QUOTA_EXCEEDED, STORAGE_UNAVAILABLE, and INTERNAL_ERROR.