metrix / docs

Deterministic · TikTok + Instagram

Trend Finder V1

Build a client-owned creator group, collect public videos and reels by actual publication time, and read separate deterministic winner lanes backed only by stored observations.

Restricted beta

Access requires a beta seat and is rate limited per user. Check each group’s coverage and poll diagnostics for current collection status. See Access & limits before integrating. Operator-set capacity decides how many creators collect at once; everything past it waits in a visible queue.

V1 contract, not Trend Detector V2

V1

Trend Finder owns group configuration, publication-cutoff coverage, scheduling, and separate deterministic winner rankings from stored observations. Creator-list scheduling follows the operator’s per-platform policy; existing generation windows retain their recorded cadence. The group’s service_level/cadence_seconds reports the TikTok policy. It does not invoke AI format fingerprints, OCR/frame/transcript analysis, format clusters, trend buckets, AI scores, or V2 cost gates.

Default lookback is seven days (accepted range 1–30). Collection is complete only when a connector crosses the inclusive create_time/taken_at cutoff or proves upstream exhaustion. Content exactly at the cutoff remains eligible and is not crossing proof; a strictly older item or exhaustion is required. A page cap, missing continuation, unproven ordering, rate limit, capacity stop, or transient error remains partial. Earlier committed pages survive a later-page failure.

Trend refresh is independent of Social API tier and refresh quota. A read never calls a provider or queues work. Every Trend Finder request needs a stable backend-derived X-End-User header; admission and group ownership are scoped to that user within the bearer client.

Access & limits

Seats. Every distinct X-End-User within your client needs a beta seat, and the operator sets the total number of seats. The planned 3,000-seat ceiling is an admission ceiling, not a promise of 3,000 simultaneously active creators: the separately configured TikTok and Instagram creator capacities decide which seated users are active and which are queued. When self-enrollment is switched on, a new user's first POST /api/v1/trendfinder/niches takes a seat automatically. Otherwise the operator enrolls users. A user without a seat gets 403 BETA_NOT_ENROLLED on writes. When every seat is taken, first-time group creation returns 403 BETA_COHORT_FULL. A user the operator removed is never re-enrolled automatically.

Per-user allowance. These limits span both platforms and all of the user's groups. Admission-sensitive successes return the user's live usage in beta_limits.

LimitValueWhen exceeded
Active groups50409 BETA_GROUP_LIMIT_EXCEEDED
Creators per group100422 CREATOR_LIMIT_EXCEEDED
Creator addsunlimited (2026-09-27) — bounded only by the group ceiling aboveno daily-add-limit error; new owned creators start queued for scheduler-tick admission
Populate runs2 per rolling 24h; each run can add many creators at onceaccepted with 202 and held until its slot opens; 429 RATE_LIMITED only when 7 days are already queued

Every new owned creator starts queued. The scheduler tick considers it for admission; a creator someone else already collects can be promoted by that tick without using an additional capacity slot. Active slots prioritize owners with fewer active creators; among equally loaded owners, the longer-waiting creator goes first (2026-09-27). At capacity, a zero-active owner's turn can also come from reclaim: the heaviest owner (2+ active) has their newest non-shared extra moved back to queued, once per scheduler tick — a user's own first active creator and any shared creator are never reclaimed. Reactivating a paused group when global capacity is full returns 429 BETA_ACTIVATION_CAPACITY_EXCEEDED. Keep the group paused and retry later.

Request rate limits. Trend Finder counts requests per end user, not per IP address, because your backend sends every user's requests from one address.

ScopeDefault limit
Each X-End-User within your client60 per minute, 600 per hour, 3,000 per day by default (operator configuration can override)
Your whole client, all users together2,400 per minute

Past a limit, the API returns 429 RATE_LIMITED with a Retry-After header (seconds) and an X-RateLimit-Limit header naming the window that was hit. A refused request does not count against any window. Winners change at most every few hours, so poll /videos about 4 times per 15 minutes per user at most. That is 384 per day, well inside the per-user limit.

Group and creator API

POST/api/v1/trendfinder/nichesCreate group.

Body: name, optional lookback_days, external_ref, interests, IANA timezone, and up to 100 {platform, handle} creators. Desired state commits as scheduler demand; the scheduler materializes acquisition on its normal tick, with no immediate crawl enqueue. A same-user retry of external_ref returns the original group; another user of the client receives 409 EXTERNAL_REFERENCE_OWNER_CONFLICT.

GET/api/v1/trendfinder/nichesList owned groups.
GET/api/v1/trendfinder/niches/{model_id}Group + creator states.
PATCH/api/v1/trendfinder/niches/{model_id}Rename/lookback/active.

Activating (is_active: true) adds a reactivation summary of the atomically materialized schedule epoch — planned, omitted, superseded, deferred, catch_up counts plus a poll_history pointer. Per-poll detail lives on the /polls ledger.

POST/api/v1/trendfinder/niches/{model_id}/creatorsIdempotent batch add.
POST/api/v1/trendfinder/niches/{model_id}/populateSeed-driven populate (async).

Body: exactly one of description (≤500 chars), hashtags (≤10), or topic_id, plus optional limit 1–100 (25). Returns 202 with a poll_url; poll GET /api/v1/jobs/{id} for the result. Optional platform: tiktok (default) or instagram. Two populate admissions per admitted end user per rolling 24h; a further one is accepted with 202 and held until its slot opens, 429 only when 7 days are already queued; one populate per niche per UTC hour is deduped to the live job.

DELETE/api/v1/trendfinder/niches/{model_id}/creators/{platform}/{handle}Remove exact member.
DELETE/api/v1/trendfinder/niches/{model_id}Delete owned group.

Creator identity is platform-qualified, so the same text handle on TikTok and Instagram is two memberships. An admitted user may have up to 50 active groups of up to 100 creators each; bulk add has no daily creator-add limit (2026-09-27). Each creator gets queued_since (null once active), and GET /niches/{model_id} carries a top-level owner_admission: {active, queued} summary. The scheduler tick admits new members by prioritizing owners with fewer active creators, then longer-waiting creators among equally loaded owners, including after cap increases or pause releases. Admission-sensitive successes return beta_limits; errors make owner usage and cohort fields unavailable rather than exposing another user's data. Cross-client reads and mutations return 404.

Search terms, suggestions and expanded videos

GET /api/v1/trendfinder/niches/{model_id}/terms returns the group's terms and their author. created_by: "auto" marks up to five automatically selected natural terms on both platforms. Seeding runs once for an active group with no terms and recent member posts: at least half its members have posts, or the group is over two hours old and at least one member has posts. Terms come from scored word suggestions. Missing background or insufficient text leaves the group unseeded for a later check. It does not add creators to the group.

GET /niche-profile includes words and word_status; cannot_judge means the evidence is insufficient, not that the niche is irrelevant. GET /term-map adds scored word nodes and links with graph_basis=words, retaining legacy hashtag nodes. Display unknown judgments as unknown even when their action lists are empty.

PUT /terms replaces the list (at most 20 enabled terms), preserves retained terms' author and creation time, and immediately refreshes suggestions for newly enabled term/platform pairs from stored sightings. It performs no live search. Daily reads or POST /terms/run collect results; one read per platform, term and UTC day serves every group holding that term.

GET /suggestions returns {suggestions, has_more}. The default sort=recommended ranks the newest 500 pending candidates by relevance then followers, with the matching hashtags and up to two post cards. Unknown counts stay null. Approve adds a new creator through normal admission or keeps an already watched Expanded creator in that scope; reject preserves the decision on later searches.

GET /expanded-videos keeps topic feeds in items (TikTok) and instagram.items, ordered by views. Independent terms.tiktok.items and terms.instagram.items return term-found videos even without a linked topic. These lanes exclude group members' posts and dislikes, rank up to 300 stored-view candidates per platform by relevance then views, and return up to limit per lane (30 by default, maximum 100). Rows include found_by_terms, matched_hashtags, relevance, post and vote. A GET reads stored data only.

Paths abbreviated above share /api/v1/trendfinder/niches/{model_id}. All use the same owned-group and X-End-User rules. See the API reference for filters and response details.

scope=natural|all defaults to all on winner and photo/carousel reads. Natural filters membership before ranking and coverage. Creator responses include membership_scope: existing choices are natural, new automatic discoveries are expanded. Explicit creator additions promote to Natural. Automatic search additions remain pending suggestions for feedback; suggestions expose nullable membership_scope and is_watched. Approving an already watched Expanded suggestion keeps its scope.

Separate winner lanes

Add format=csv to export the same winners, optionally selecting platform=tiktok or instagram. The times_usual column is the JSON winner’s creator-relative multiple (current_views / creator_usual_views), empty when unknown and on Instagram; baseline_views remains the same post’s earlier observation.

GET/api/v1/trendfinder/niches/{model_id}/videosDatabase-only winners.

Returns lanes.tiktok and lanes.instagram. limit is 1–100 (100): TikTok returns at most that many, while Instagram may return up to three score-basis/unranked buckets. No sort override or live pagination. Each item also carries an optional post feed card (url, caption, creator, views/likes/comments/shares/saves, hashtags, thumbnail and stream links; thumbnail_url is null when no unexpired image is available). Missing values are null; post is null when no stored metadata exists.

TikTok orders by current views, then nullable six-hour views per hour, then stable content ID. Instagram ranks separately within each engagement score basis and also returns an unranked bucket. The baseline is the newest compatible observation at or before latest minus six hours; rate divides by actual elapsed hours. Instagram photos/carousels participate in the engagement lane.

Every item includes publication/observation times, age, cadence, nullable baseline/span/rate, freshness, and metric status. TikTok items and scored Instagram items include a rank; unranked Instagram items have a null rank. Coverage includes exact member-state counts, complete/partial truth, oldest due age, last success, and bounded stop/acquisition/refresh-error summaries.

GET/api/v1/trendfinder/niches/{model_id}/pollsDiagnostics: why a poll ran, deferred, or failed.

Optional platform, lane, and state filters; limit 1–100 (100). Returns items[] and a summary count per state.

/videos is the product; /polls is the diagnostics. Use it when /videos looks wrong — empty, stale, a creator missing — to see why each scheduled collection ran, was deferred, or failed. A tiktok_creator/instagram_creator lane collects a creator's post list; a tiktok_target/instagram_target lane refreshes numbers on posts already found. Each item carries a stable reason_code to branch on (e.g. capacity_unavailable, breaker_open, pool_empty, group_paused, covered_by_catch_up, provider_terminal, retry_budget_exhausted) plus a matching explanation sentence for display; both are null on a poll that needs no explanation. Model-and-client scoped — never account, proxy, lease, queue, or raw provider-error data.

Do not flatten operational truth

Creator states: pending, collecting, ready, partial, empty, error, unsupported. Winner metric states: ready, insufficient_history, stale, counter_reset, metric_unavailable. Freshness: fresh, stale, unavailable.

Missing metrics remain null, explicit zero remains zero, and a counter reset never becomes negative velocity. Failed/capacity-blocked work remains due and visible.

{
  "error": "INVALID_REQUEST",
  "message": "Request parameters are invalid.",
  "request_id": "<REQUEST_ID>",
  "beta_limits": {
    "group_limit": 50,
    "max_creators_per_group": 100,
    "guaranteed_active_creators_per_user": 1,
    "fair_share": true,
    "populates_per_rolling_24h": 2,
    "usage": null,
    "beta_user_limit": null,
    "unavailable": ["usage", "beta_user_limit"]
  }
}

Every error repeats that UUID in X-Request-ID and carries the static beta_limits (abridged above), with the user's usage left unavailable. Branch on error, never on message. Codes: INVALID_REQUEST 400 (missing or invalid X-End-User) or 422,CREATOR_LIMIT_EXCEEDED 422, UNAUTHENTICATED 401, BETA_NOT_ENROLLED / BETA_COHORT_FULL / FORBIDDEN 403, NOT_FOUND 404, CONFLICT / EXTERNAL_REFERENCE_OWNER_CONFLICT / BETA_GROUP_LIMIT_EXCEEDED / INSTAGRAM_CAPACITY_EXCEEDED 409, RATE_LIMITED / BETA_ACTIVATION_CAPACITY_EXCEEDED 429, INTERNAL_ERROR 500, STORAGE_UNAVAILABLE / INSTAGRAM_CAPACITY_UNCONFIGURED 503. Provider, proxy, credential, SQL, and unbounded exception text are never returned.

Creator exclusions and saved intent

Save content_request and purpose_text (up to 2,000 characters each) on a niche. These save context for relevance review; they do not enable semantic filtering.

Use POST /niches/{id}/creator-feedback with a client action UUID to exclude a creator from this group, undo a rejection, explicitly restore an exclusion, or annotate an action. Reject targets a stored post or pending suggestion. GET /niches/{id}/creator-exclusions returns current exclusions and markers for safe restoration.

Rejection removes this group's tracking immediately and wins across feeds and admission. Undo restores the previous exclusion state; neither undo nor restore restarts tracking. Post-only votes retain their existing behavior. Action history remains after correction. Keys follow stored platform handles; creator renames are not reconciled.

Discovery returns at most two posts per creator per lane; Natural monitoring remains complete apart from exclusions. Rollback must preserve acknowledged exclusions and history.

Instagram capacity is an operator gate

Runtime verification required

A live route and automated fixture coverage do not prove live Instagram ordering, pinned-item behavior, continuation, or daily capacity. Verify the worker-equivalent provider canary and current configuration before asserting availability.

Missing or invalid configuration returns 503 INSTAGRAM_CAPACITY_UNCONFIGURED; zero capacity is configured-but-disabled and returns 409 INSTAGRAM_CAPACITY_EXCEEDED. Before activating the lane, an operator must record the bounded canary and select an environment-specific ceiling. See the operations runbook.