API v1
TrendWatch API
Read your TrendWatch workspace programmatically: connected Instagram and TikTok accounts, collected videos with their metrics, transcripts and AI script formulas, and start AI analysis from your own scripts.
Use the REST API to work with the same data available in the TrendWatch application. Requests authenticate with an API key.
API and MCP access is included in every paid plan. On the free plan
keys cannot be created, and existing keys answer
403 PLAN_REQUIRED.
https://server.trndwtch.com/api/v1
Beta limitations
Except for starting AI analysis, the published API v1 endpoints are read-only. There are currently no API endpoints for:
- Adding, editing, pausing or removing tracked Instagram and TikTok accounts.
- Submitting a new video by URL.
- Changing a video's production status or its position on the Kanban board.
Quickstart
Create a key on your Account page, then check
that it works. /me costs nothing and returns your plan
and credit balance.
export TWA_KEY="twa_..."
curl -s "https://server.trndwtch.com/api/v1/me" \
-H "Authorization: Bearer $TWA_KEY"
Then list your ten most recently collected videos:
curl -s "https://server.trndwtch.com/api/v1/videos?sort_by=created_at&order=desc&limit=10" \
-H "Authorization: Bearer $TWA_KEY"
The same call in JavaScript and Python:
const res = await fetch(
"https://server.trndwtch.com/api/v1/videos?sort_by=created_at&order=desc&limit=10",
{ headers: { Authorization: `Bearer ${process.env.TWA_KEY}` } },
);
if (!res.ok) throw new Error((await res.json()).message);
const { data, total_count, has_more } = await res.json();
import os, requests
r = requests.get(
"https://server.trndwtch.com/api/v1/videos",
params={"sort_by": "created_at", "order": "desc", "limit": 10},
headers={"Authorization": f"Bearer {os.environ['TWA_KEY']}"},
timeout=30,
)
r.raise_for_status()
payload = r.json()
Starter prompt for Claude Code or Codex
After saving your API key in the TWA_KEY environment
variable, paste this prompt into Claude Code or Codex:
Use the TrendWatch REST API at https://server.trndwtch.com/api/v1.
Read the API key from the TWA_KEY environment variable. Never print or expose the key.
1. Call GET /me with the Authorization: Bearer $TWA_KEY header to verify authentication.
2. Call GET /videos?sort_by=created_at&order=desc&limit=5 with the same header.
3. Show the five most recently collected videos in a compact table with account name, caption, platform_url, views, likes, comments, and posted_at.
If a request fails, show the HTTP status and API error message without exposing the key.
Authentication
Every request carries a bearer token: an API key created on the
Account page. Keys start with twa_ and are shown
exactly once at creation; only a SHA-256 hash is stored, so a lost
key cannot be recovered, only replaced.
Authorization: Bearer twa_<your key>
You can hold up to five active keys and revoke any of them instantly. A key acts strictly as its owner: it reads that account's data and spends that account's credits, and it can never reach another user's workspace.
The key's owner must be on an active paid plan. The check runs on
every request, so a key created while subscribed stops working —
with 403 and data.code: PLAN_REQUIRED — if
the subscription lapses, and starts working again after an upgrade.
No key is revoked in the process.
Conventions
- Every successful response wraps its payload in
data. -
Listing endpoints add
page,limit,total_countandhas_morenext todata./accountsis unpaginated and returns everything, so itstotal_countequals the array length. -
Timestamps are ISO 8601 in UTC. Date filters take
YYYY-MM-DD. -
Fields the platform never reported come back as
nullrather than being omitted, except the long text fields on the video detail endpoint, which are absent unless requested withinclude. -
Videos come from two sources that share one id space:
accounts(collected from a connected account) andsubmitted(added by URL). Detail and analysis endpoints accept an id from either and report which one it was insource.
Metrics
Two of the numbers on every collected video compare it against its own account's norm rather than against the platform. That is what makes a 5k-follower account and a 5M-follower account directly comparable.
| Name | Type | Description |
|---|---|---|
| outlier_score | number |
Reach against the account's own norm. 1 = a
typical video for that account, 3 = about three
times its usual reach.
|
| er_outlier_score | number, percent |
Engagement against the account's own norm. 0 =
as usual, +100 = about twice the usual
engagement, -50 = about half of it.
|
| er | number, percent |
Engagement rate: likes and comments relative to views. An
absolute figure, unlike er_outlier_score.
|
| engagement | low | average | best | Absolute engagement band. |
| virality | low | average | good | best | virus |
Coarse reach band against the account's norm, assigned when
the video is collected. no_data until the
account has a norm.
|
engagement is absolute while
er_outlier_score is relative, so
engagement: "best" next to a negative
er_outlier_score is not a contradiction: the video is
strong for the platform yet below what that particular account
usually gets.
The breakout videos of every account you track, best first:
curl -s "https://server.trndwtch.com/api/v1/videos?min_outlier_score=3&sort_by=outlier_score&order=desc" \
-H "Authorization: Bearer $TWA_KEY"
All scores are null until the account has enough
collected videos to have a norm.
Errors
Errors use standard HTTP status codes. The enumerated API failures
below carry a stable data.code; match on that code
because the human-readable message may be reworded. Validation
failures return data.errors instead.
{
"message": "This API key has been revoked.",
"data": { "code": "REVOKED_API_KEY" }
}
The same envelope carries every other failure. Running out of
credits while starting an analysis, for example, answers
402 with:
{
"message": "No credits left for this billing period.",
"data": { "code": "INSUFFICIENT_CREDITS" }
}
Status codes
| Name | Type | Description |
|---|---|---|
| 400 | Bad Request |
A query parameter failed validation;
data.errors lists the offending fields.
|
| 401 | Unauthorized |
MISSING_API_KEY, MALFORMED_API_KEY,
INVALID_API_KEY or
REVOKED_API_KEY.
|
| 402 | Payment Required |
INSUFFICIENT_CREDITS — no credits left this
billing period.
|
| 403 | Forbidden |
PLAN_REQUIRED — the key's owner is not on an
active paid plan.
|
| 404 | Not Found |
VIDEO_NOT_FOUND — no such video in your
workspace.
|
| 429 | Too Many Requests | RATE_LIMITED — see rate limits below. |
| 500 | Server Error | Unexpected failure; retry, then contact support. |
| 502 | Bad Gateway |
UPSTREAM_QUERY_FAILED — the video store was
unreachable. Safe to retry.
|
Rate limits
200 requests per minute per API key, plus a 1000 per minute ceiling
per IP address applied before the key is looked up. Both windows are
one minute and report their state in the standard
RateLimit response headers. Exceeding either returns
429.
429 and retry.
/api/v1/me
Your plan and credit balance. The cheapest way to verify a key works.
{
"data": {
"email": "you@example.com",
"username": "you",
"plan": "pro",
"credits": {
"available": 812,
"used_this_period": 188,
"plan_limit": 1000,
"package_credits": 0,
"total_monthly_limit": 1000,
"period_started_at": "2026-07-18T00:00:00.000Z",
"resets_at": "2026-08-18T00:00:00.000Z"
}
}
}
available = plan_limit + package_credits − used_this_period,
floored at zero, and a new period resets usage by itself.
/api/v1/accounts
Every social account connected to your workspace, with follower counts, collection status and aggregate engagement. Takes no parameters.
{
"data": [
{
"id": "a1b2c3d4",
"name": "Nike",
"platform": "instagram",
"username": "nike",
"url": "https://instagram.com/nike",
"followers": 302000000,
"collection_status": "done",
"is_active": true,
"video_count": 143,
"posting_frequency_per_week": 4.5,
"average_er": 2.1,
"created_at": "2026-05-02T10:14:00.000Z"
}
],
"total_count": 1
}
collection_status is the scrape lifecycle
(new, pending, processing,
done, error). is_active is
false while an account is paused and collecting
nothing. Use id as account_id when listing
videos.
/api/v1/videos
Paginated video search across your workspace.
Query parameters
| Name | Type | Description |
|---|---|---|
| source | accounts | submitted |
Which set to read. Default accounts.
submitted supports pagination only in v1;
search, sort and date filters do not apply to it.
|
| page | integer | 1-based page number. Default 1. |
| limit | integer | Page size, 1–100. Default 20. |
| sort_by | string |
created_at (default), posted_at,
views, likes,
comments, shares,
duration, followers,
er, outlier_score,
er_outlier_score. Combine the last two with
order=desc to put the strongest videos first.
|
| order | asc | desc | Default desc. |
| search | string | Text search over captions, scripts, topics and account names. Max 200 characters. |
| account_id | string | Restrict to one connected account. |
| date_from | YYYY-MM-DD | Publication date on or after this day. |
| date_to | YYYY-MM-DD | Publication date on or before this day. |
| min_outlier_score | number |
Only videos whose views are at least this many times the
account's own norm. min_outlier_score=3 is the
standard way to ask for "just the breakout videos".
source=accounts only.
|
| max_outlier_score | number |
Upper bound on the same scale — e.g.
max_outlier_score=1 for underperformers.
source=accounts only.
|
| min_er_outlier_score | number |
Only videos whose engagement beat the account's own norm by
at least this many percent.
min_er_outlier_score=50 = at least 50% above
usual. source=accounts only.
|
| max_er_outlier_score | number |
Upper bound on the same scale.
source=accounts only.
|
| engagement | string |
Comma-separated absolute engagement
buckets: low, average,
best. source=accounts only.
|
| virality | string |
Comma-separated virality buckets: low,
average, good, best,
virus. A coarse pre-computed label — prefer
min_outlier_score when the threshold has to be
exact. source=accounts only.
|
date_from and date_to filter by
publication date. For what TrendWatch collected this week, sort by
created_at instead; collection date is sortable, not
filterable.
{
"data": [
{
"id": "v_9f8e7d",
"source": "account",
"account": {
"id": "a1b2c3d4",
"name": "Nike",
"platform": "instagram",
"username": "nike",
"followers": 302000000
},
"platform_url": "https://instagram.com/reel/XYZ",
"caption": "Just do it.",
"topic": "motivation",
"summary": "Athlete montage with a voiceover hook.",
"metrics": {
"views": 1840000,
"likes": 96000,
"comments": 1200,
"shares": 4300,
"duration_sec": 28,
"outlier_score": 4.2,
"er_outlier_score": 38.5,
"er": 5.28,
"engagement": "best",
"virality": "good"
},
"posted_at": "2026-07-30T09:12:00.000Z",
"created_at": "2026-07-30T11:40:00.000Z",
"analysis_status": "done",
"has_transcript": true,
"has_formula": true,
"has_script": false
}
],
"page": 1,
"limit": 20,
"total_count": 143,
"has_more": true
}
Videos with source: "submitted" carry url,
platform, owner_username,
owner_followers and collection_status
instead of the account object, and a smaller metrics
set.
/api/v1/videos/{id}
One video in full, from either source.
Query parameters
| Name | Type | Description |
|---|---|---|
| id | path, string | Video id from the listing endpoint. |
| include | string |
Comma-separated long fields to fetch:
transcript, formula,
script. Unknown values are ignored.
|
Long text fields are omitted from the payload entirely unless
requested, so a listing stays small. The
has_transcript, has_formula and
has_script booleans are always present, letting you see
what exists before fetching it.
curl -s "https://server.trndwtch.com/api/v1/videos/v_9f8e7d?include=transcript,formula" \
-H "Authorization: Bearer $TWA_KEY"
/api/v1/videos/{id}/analysis
Start AI analysis: transcription plus script-formula extraction. Spends one credit.
Analysis is asynchronous and takes a few minutes. A fresh start
answers 202 with already_running: false
and charges one credit. If a run is already in flight the call
answers 200 with already_running: true and
charges nothing, so retrying is safe. Re-analysing a video that
already finished starts a new run and charges again.
{ "data": { "source": "account", "status": "to_transcribe", "already_running": false } }
/api/v1/videos/{id}/analysis
Poll for the result. Free to call. Terminal states are
done (the formula is ready) and error (the
error field says why); anything else means the pipeline
is still working.
{
"data": {
"source": "account",
"status": "done",
"formula": {
"caption": "Three habits that changed my mornings",
"formula": "Question hook → quick proof → three-step payoff",
"text_hook": "Still checking your phone first thing?",
"audio_hook": "Direct question over a quiet intro",
"visual_hook": "Phone placed face-down beside an alarm clock"
},
"error": null
}
}
# start, then poll until the status is terminal
curl -s -X POST "https://server.trndwtch.com/api/v1/videos/$VIDEO_ID/analysis" \
-H "Authorization: Bearer $TWA_KEY"
until curl -s "https://server.trndwtch.com/api/v1/videos/$VIDEO_ID/analysis" \
-H "Authorization: Bearer $TWA_KEY" | grep -qE '"status":"(done|error)"'; do
sleep 30
done
OpenAPI spec
The full contract is published as OpenAPI 3.1 at
https://server.trndwtch.com/api/v1/openapi.json. It needs no authentication, so you can import it into Postman,
Insomnia or Bruno, or generate a typed client before you have a key.
npx @openapitools/openapi-generator-cli generate \
-i https://server.trndwtch.com/api/v1/openapi.json \
-g typescript-fetch -o ./trendwatch-client
Security
- An API key grants read access to your whole workspace and the ability to spend your credits on analysis. Treat it like a password: keep it in an environment variable or a secret manager, never in client-side code or a public repository.
- Revoke a key the moment you suspect it leaked; revocation takes effect immediately. Each key shows a last-used timestamp on the Account page, which is the only leak signal available.
- Captions, transcripts and scripts returned by the API are third-party or user-generated content. If you pass them to an LLM, treat them as data, never as instructions.
- No endpoint deletes anything, and no endpoint can reach another user's data; every query is scoped to the key's owner in the database.
MCP server
The same data is available to AI agents over MCP at
POST https://server.trndwtch.com/mcp (streamable HTTP,
stateless), using the same bearer token. Five tools:
trendwatch_list_accounts,
trendwatch_search_videos,
trendwatch_get_video,
trendwatch_run_ai_analysis (spends one credit, flagged
destructive) and trendwatch_get_usage.
claude mcp add --transport http trendwatch https://server.trndwtch.com/mcp \
--header "Authorization: Bearer twa_..."
ChatGPT developer mode: Settings → Connectors → Advanced → Developer
mode, add https://server.trndwtch.com/mcp with auth type
API key and the twa_… value.