MCP server
Let AI assistants analyze videos directly through Claude, Codex or any other MCP client.
The hosted MCP server lets an AI assistant run and read video analyses. There is nothing to install and no API key: you sign in with your NetraFlow account the first time your client connects.
Connect
Open NetraFlow in Claude's connector directory and choose Connect, then sign in with your NetraFlow account and approve the access.
To add it by hand instead, open Customize → Connectors, choose Add custom connector, name it netraflow and paste https://mcp.netraflow.com/mcp.
Add the server from your terminal:
claude mcp add --transport http netraflow https://mcp.netraflow.com/mcpThen run /mcp in a Claude Code session, pick netraflow, and sign in with your NetraFlow account.
Claude Desktop shares its connectors with Claude.ai: connect NetraFlow there (the Claude.ai tab) and it is available
in the desktop app. To add it by hand, open Customize → Connectors, choose Add custom connector, name it
netraflow and paste https://mcp.netraflow.com/mcp.
Add the server, then sign in:
codex mcp add netraflow --url https://mcp.netraflow.com/mcp
codex mcp login netraflowAny assistant that speaks MCP over HTTP works. Add the server URL to its MCP settings; most take this shape:
{
"mcpServers": {
"netraflow": {
"url": "https://mcp.netraflow.com/mcp"
}
}
}The first time it connects, a NetraFlow sign-in page opens.
Clients that only speak the older SSE transport should point at https://mcp.netraflow.com/sse instead.
Sign-in and workspace
Sign-in is OAuth: a NetraFlow sign-in page, then a page naming the workspace the client will act on, to approve.
An approved connection can do everything the tools below do in that workspace, spending credits included. There are no scopes to choose.
- Which workspace. The first one you joined.
check_creditsreturns itstenant_idandname. You can't switch yet. - How long it lasts. You approve again every 30 days. Access ends when you leave the workspace.
Tools
| Tool | Kind | Description |
|---|---|---|
analyze_video | submit | Submit a YouTube, TikTok or Instagram link for analysis. Returns a job_id. |
check_status | read | A job's status and progress, without its results. |
get_analysis | read | A job with its results. |
list_analyses | read | Your jobs, newest first, with filters and pages. |
cancel_job | write | Cancel a pending, processing or held job that is not in a batch. |
create_batch | submit | Submit up to 50 links with the same capabilities, and questions across all of them. |
get_batch | read | A batch's status, its jobs, and the answers across the set. |
list_batches | read | Your batches, newest first, with a status filter and pages. |
cancel_batch | write | Cancel a batch and every job in it that has not ended. |
check_credits | read | The workspace, its plan, its credit balance and its limits. |
get_usage | read | Credit totals for a period and the transactions behind them. |
follow_creator | write | Follow a channel or profile and analyze new videos that match a rule. |
list_follows | read | Every follow, with its rule, channels, counts and the plan's follow limits. |
update_follow | write | Pause or resume a follow, replace its rule, or replace its channels. |
unfollow_creator | write | Delete a follow. Its analyses are kept. |
get_follow_activity | read | The videos a follow checked recently, with each verdict and reason. |
Submit spends credits. Write changes something without spending (a follow's matches are charged later). Read changes nothing. The same plan limits apply as on the API. Tools take links only: upload a file of your own in the console or through the Uploads API.
What a tool returns
One block of JSON: the same body as the REST endpoint each tool below links to, with data and meta.request_id, and
pagination on lists. Follows have no REST endpoint, so their examples are here.
Errors
A refused call comes back as a tool error, one line of text:
INVALID_QUERY: queries[1] looks like an attempt to change the model's instructions. Ask about the video instead. {"index":1} (request_id: req_8d2kQ0pZ1x7T)In order: the code, the message, details as JSON when there are any (as on the
API, plus follow_id on FOLLOW_EXISTS), a console link when the fix is there
(Top up at https://netraflow.com/billing), and the request id to quote to support.
Two other shapes: MCP error -32602: Input validation error: … for an argument that fails its own check (a missing
required one, a malformed URL, an empty capabilities, an unknown capability or include value, or more than 50
urls; no request id), and a plain sentence such as
NetraFlow is temporarily unavailable. Please try again later.
Job and batch codes are explained on Jobs and Batch. Follow codes:
| Code | Meaning |
|---|---|
FOLLOW_NOT_FOUND | No follow with that follow_id in this workspace |
FOLLOW_EXISTS | The workspace already follows this creator with the same rule and question. details.follow_id names that one |
PLAN_REQUIRED | Slack and webhook channels need Starter or above, or the plan's follow limit is used |
CONFLICT | The follow can't be resumed, because its creator was deleted |
Jobs
analyze_video
Submit a video for analysis, as POST /v1/jobs does for a link.
| Prop | Type | Description |
|---|---|---|
| url | string | A YouTube, TikTok or Instagram video link. |
| capabilities | string[] | At least one of: transcription, summary, brands, metadata, custom_query, objects. |
| queries | string[] | Your own questions about the video. Up to 10, 500 characters each, within your plan's limit. |
| brands | string[] | Brand names to report. Up to 50, 100 characters each. |
| surfaces | string[] | Surface slugs to look on, such as "jersey". Up to 10. |
| objects | string[] | Things to track, in your own words. Up to 5, 100 characters each. Required by objects. |
| from_job | string | An earlier job of yours on the same video. Charged the same either way. |
| language | string | Language hint for transcription, such as "en". Up to 10 characters. |
| fps | integer (1-4) | Frames looked at per second, clamped to your plan's max_fps. Defaults to 1. |
| webhook_url | string | An https URL for job webhooks. Starter and above. |
| return_proof | boolean | Include the storyboard and speech_context. Defaults to true. |
| dry_run | boolean | Return the credit estimate and create nothing. Defaults to false. |
| max_credits | integer | Fail the job, free, if its estimate on the real length passes this. |
Arguments mean what they mean on POST /v1/jobs. With dry_run: true it
returns the estimate and no job_id.
Errors: VALIDATION_FAILED, INVALID_URL, INVALID_CAPABILITIES, INVALID_QUERY, TOO_MANY_QUERIES,
QUERIES_DISABLED, TOO_MANY_BRANDS, INVALID_SURFACE, MODE_NOT_ALLOWED, JOB_NOT_FOUND (an
unknown from_job), INSUFFICIENT_CREDITS, SUBSCRIPTION_PAST_DUE, RATE_LIMITED, CONCURRENT_LIMIT_REACHED,
BODY_TOO_LARGE.
check_status
A job without its results: its status, and while it is processing, a progress object.
| Prop | Type | Description |
|---|---|---|
| job_id | string | The job to check. |
progress is as on GET /v1/jobs/:id.
Errors: JOB_NOT_FOUND.
get_analysis
A job with its results, as GET /v1/jobs/:id returns it, except that per-frame
data is left out unless you ask for it.
| Prop | Type | Description |
|---|---|---|
| job_id | string | The job to read. |
| include | "frames"[] | Add the per-frame data that is left out by default. Large: a minute of tracking can run to megabytes. |
Per-frame data can run to megabytes, more than an assistant can take in, so by default it leaves out
objects[].appearances[].frames, brands[].appearances[].frame_detections and storyboard.timestamps_ms. Each
appearance keeps its times, bounding_box and confidence. include: ["frames"] adds them back; the REST API always
returns everything.
A capability that could not run on a job that completed with the rest is listed in meta.failed_capabilities (its
capability, code and message), and its result key is absent. A brands or objects run that was cut short completes
with meta.brand_detection_degraded or meta.object_detection_degraded set.
What each result holds is on the capability pages. Fetching a storyboard sheet takes an API key: see Frames.
Errors: JOB_NOT_FOUND.
list_analyses
Your jobs, newest first, as GET /v1/jobs lists them.
| Prop | Type | Description |
|---|---|---|
| status | "pending" | "processing" | "completed" | "failed" | "cancelled" | "held" | Only jobs with this status. |
| follow_id | string | "any" | Only the jobs this follow created, or with "any", every job a follow created. |
| limit | integer (1-100) | Jobs per page. Defaults to 20. |
| cursor | string | pagination.next_cursor from the previous page. |
| created_after | string (ISO 8601 timestamp) | Only jobs created after this time. |
| created_before | string (ISO 8601 timestamp) | Only jobs created before this time. |
Each item is a summary, described under List items. Use get_analysis for the
rest.
Errors: VALIDATION_FAILED.
cancel_job
Cancel a pending, processing or held job, as
POST /v1/jobs/:id/cancel does. A job in a batch can't be cancelled
on its own: cancel its batch with cancel_batch.
| Prop | Type | Description |
|---|---|---|
| job_id | string | The job to cancel. |
A job that was running is charged for the work it did, a few seconds after the cancel: read it with get_analysis.
Errors: JOB_NOT_FOUND, JOB_NOT_CANCELLABLE (already ended, or in a batch, whose id the message names).
Batches
create_batch
Submit several links at once, as POST /v1/batch does. Every video gets the
same capabilities, questions and objects.
| Prop | Type | Description |
|---|---|---|
| urls | string[] | Video links. Up to 50, within your plan's batch size. |
| capabilities | string[] | At least one of: transcription, summary, brands, metadata, custom_query, objects. |
| queries | string[] | Questions asked of every video. Up to 10, 500 characters each. |
| objects | string[] | Things to track in every video. Up to 5, 100 characters each. |
| fps | integer (1-4) | Frames looked at per second, clamped to your plan's max_fps. Defaults to 1. |
| aggregate | { enabled, queries } | Questions answered once across the whole set, for 5 credits. Send enabled: true and at least one question. |
| webhook_url | string | An https URL for batch.completed and batch.paused. Starter and above. |
| return_proof | boolean | Include the storyboard and speech_context. Defaults to true. |
| dry_run | boolean | Return the per-video and total estimate and create nothing. Defaults to false. |
| max_credits | integer | Refuse the batch if its total estimate passes this. |
A video listed twice runs once, and jobs has one entry per video.
Errors: VALIDATION_FAILED, INVALID_URL, INVALID_CAPABILITIES, INVALID_QUERY (a question in queries or
aggregate.queries, which the message names), TOO_MANY_QUERIES, TOO_MANY_URLS, TOO_MANY_AGGREGATE_QUERIES,
QUERIES_DISABLED, BATCH_NOT_ALLOWED, MODE_NOT_ALLOWED, BUDGET_EXCEEDED,
INSUFFICIENT_CREDITS, SUBSCRIPTION_PAST_DUE, RATE_LIMITED, CONCURRENT_LIMIT_REACHED, BODY_TOO_LARGE.
get_batch
A batch with every job's status and the answers across the set, as
GET /v1/batch/:id returns it.
| Prop | Type | Description |
|---|---|---|
| batch_id | string | The batch to read. |
The status values are under Batch status values.
Errors: BATCH_NOT_FOUND.
list_batches
Your batches, newest first, as GET /v1/batch lists them.
| Prop | Type | Description |
|---|---|---|
| status | "processing" | "completed" | "failed" | "cancelled" | "paused" | Only batches with this status. |
| limit | integer (1-100) | Batches per page. Defaults to 20. |
| cursor | string | pagination.next_cursor from the previous page. |
Errors: VALIDATION_FAILED.
cancel_batch
Cancel a processing or paused batch and every job in it that hasn't ended, as
POST /v1/batch/:id/cancel does.
| Prop | Type | Description |
|---|---|---|
| batch_id | string | The batch to cancel. |
Cancelling a batch that is already cancelled succeeds again, with cancelled_jobs: 0.
Errors: BATCH_NOT_FOUND, BATCH_NOT_CANCELLABLE (it already ended completed or failed).
Account
check_credits
The workspace the tools act on, its plan, its credits and its limits, as
GET /v1/account returns them. No arguments.
get_usage
Credit totals for a period and the transactions behind them, as
GET /v1/usage returns them.
| Prop | Type | Description |
|---|---|---|
| start_date | string (ISO 8601 timestamp) | Period start. Defaults to the start of this month. |
| end_date | string (ISO 8601 timestamp) | Period end. Defaults to now. |
| type | "charge" | "purchase" | "refund" | "grant" | "forfeit" | Only transactions of this type. |
| limit | integer (1-100) | Transactions per page. Defaults to 50. |
| cursor | string | pagination.next_cursor from the previous page. |
Errors: VALIDATION_FAILED.
Following creators
A follow watches a YouTube channel or TikTok profile: each new upload is checked against your rule for free, and each match is answered and charged. Follows covers rules, channels and the daily digest.
MCP can't push to your assistant, so a match next week can't reach this conversation. Set notify to send matches to
Slack, a webhook or a teammate's email. Without it they show up only in the console and the daily digest.
follow_creator
Follow a creator. The rule is judged against each new video's title and description; each match answers question.
| Prop | Type | Description |
|---|---|---|
| url | string | A YouTube channel or TikTok profile link, such as https://www.youtube.com/@jawed. Not a video, and not Instagram. |
| rule_description | string | What the user wants, in their own words. Up to 1,000 characters. Send an empty string to rely on the filters alone, or, with no filters, to answer every new upload. |
| hashtags | string[] | Exact hashtags. Up to 20, 100 characters each. |
| mentions | string[] | Exact @mentions. Up to 20, 100 characters each. |
| keywords | string[] | Whole words in the title or description. Up to 20, 100 characters each. |
| requires_disclosure | boolean | Only videos that disclose an ad or a paid partnership. Defaults to false. |
| question | string | What to find out from every matched video, in the user's words, such as "Who sponsored the video?". Up to 500 characters. |
| notify | NotifyItem[] | Where to send each answered match. Up to 5. See The notify item. |
How the parts combine is under Rules. Returns the new follow:
{
"data": {
"follow_id": "fol_603692fd9dd14f8d",
"platform": "youtube",
"creator_handle": "@jawed",
"creator_name": "jawed",
"creator_avatar_url": "https://yt3.ggpht.com/…",
"creator_url": "https://www.youtube.com/channel/UC4QobU6STFB0P71PMvOGN5A",
"rule": {
"description": "zoo animals",
"hashtags": [],
"mentions": [],
"keywords": [],
"requires_disclosure": false
},
"rule_summary": "zoo animals",
"rule_version": 1,
"question": "Who sponsored the video?",
"max_duration_minutes": 120,
"notify": [{ "type": "slack", "target": "https://hooks.slack.com/…a1b2" }],
"status": "active",
"paused": false,
"status_reason": null,
"consecutive_failures": 0,
"last_error": null,
"last_error_at": null,
"failing_since": null,
"creator_gone": false,
"last_polled_at": null,
"last_matched_at": null,
"created_at": "2026-10-08T12:00:00.000Z",
"weekly_cap": 100,
"stats": { "checked": 0, "matched": 0, "analyzed": 0, "this_week": 0 }
},
"meta": { "request_id": "req_xyz789" }
}Following a creator again with the same rule and question returns FOLLOW_EXISTS. A different rule or question is a
second follow: it counts toward your plan's follows, and a video both match is charged once for each.
Errors: VALIDATION_FAILED, INVALID_URL (not a channel or profile that can be found, or an Instagram link, which
follows don't support yet), INVALID_QUERY (a description or question written as an instruction to the model rather
than about videos), PLAN_REQUIRED, FOLLOW_EXISTS.
list_follows
Every follow in the workspace, and the plan's follow limits. No arguments.
{
"data": {
"follows": [
{
"follow_id": "fol_603692fd9dd14f8d",
"platform": "youtube",
"creator_handle": "@jawed",
"rule_summary": "zoo animals",
"status": "active",
"paused": false,
"weekly_cap": 100,
"stats": { "checked": 12, "matched": 2, "analyzed": 2, "this_week": 1 }
}
],
"limits": { "max_follows": 50, "max_follow_jobs_per_week": 100 }
},
"meta": { "request_id": "req_xyz789" }
}Each item is a whole follow, shortened here. limits.max_follow_jobs_per_week applies to
each follow, and equals its weekly_cap.
update_follow
Pause or resume a follow, replace its rule or its question, or replace its channels. Send only what changes.
| Prop | Type | Description |
|---|---|---|
| follow_id | string | The follow to change. |
| status | "active" | "paused" | Paused keeps checking new videos but runs nothing. |
| rule_description | string | Part of the new rule. Up to 1,000 characters. |
| hashtags | string[] | Part of the new rule. Up to 20, 100 characters each. |
| mentions | string[] | Part of the new rule. Up to 20, 100 characters each. |
| keywords | string[] | Part of the new rule. Up to 20, 100 characters each. |
| requires_disclosure | boolean | Part of the new rule. |
| question | string | The new question. Matches from then on answer it; earlier answers stay. Up to 500 characters. |
| notify | NotifyItem[] | The new channel list. [] removes every channel. |
The rule is replaced, not merged: send any part and the parts you leave out are cleared. A new rule rechecks recent
videos, and new matches are offered in the console, never run by themselves. notify is replaced too; leave it out to
keep the channels. Returns the updated follow.
Errors: FOLLOW_NOT_FOUND, VALIDATION_FAILED (including a masked notify target, or nothing to change),
INVALID_URL (a notify URL that is not allowed), INVALID_QUERY, PLAN_REQUIRED, FOLLOW_EXISTS (the new rule is
the one another follow of this creator has), CONFLICT.
unfollow_creator
Delete a follow and its check history. Its analyses are kept. To stop spending but keep the history, pause it instead.
| Prop | Type | Description |
|---|---|---|
| follow_id | string | The follow to delete. |
{
"data": { "follow_id": "fol_603692fd9dd14f8d", "deleted": true },
"meta": { "request_id": "req_xyz789" }
}Errors: FOLLOW_NOT_FOUND.
get_follow_activity
The follow's 50 most recent checks, newest first, rejected ones included. A rule edit re-checks a video, so one video can appear twice.
| Prop | Type | Description |
|---|---|---|
| follow_id | string | The follow to read. |
{
"data": [
{
"id": "chk_9f2c71a04b3e5d68",
"video_id": "jNQXAC9IVRw",
"video_url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
"video_title": "Me at the zoo",
"video_thumbnail_url": "https://i.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg",
"video_published_at": "2005-04-24T03:31:52.000Z",
"video_duration_seconds": 19,
"verdict": "matched",
"reason": "Filmed in front of the elephant enclosure at a zoo.",
"skip_reason": "backfill",
"job_id": null,
"credits_charged": null,
"rule_version": 1,
"created_at": "2026-10-08T12:00:05.000Z",
"job_status": null
}
],
"meta": { "request_id": "req_xyz789" }
}Each row is described under The activity row.
Errors: FOLLOW_NOT_FOUND.
The notify item
Each item in notify is one place a finished match is sent:
| Prop | Type | Description |
|---|---|---|
| type | "slack" | "webhook" | "email" | What kind of channel it is. |
| target | string | Where it goes. The rules for each type are below. |
slack: ahooks.slack.comincoming-webhook URL. Posts the video, why it matched and the answer.webhook: anhttps://URL on a public address. Receives thefollow.match.completedevent, signed and verified like every delivery.email: someone already in the workspace.
Up to 5 channels. A match is sent once its answer is written; a failed job sends nothing.
Responses mask Slack and webhook URLs (https://hooks.slack.com/…a1b2), since whoever holds one can post there. A
masked target sent back is refused with VALIDATION_FAILED: leave notify out to keep the channels, or send the whole
list with full URLs.
The follow object
follow_creator and update_follow return one, and list_follows returns every follow in this shape.
| Prop | Type | Description |
|---|---|---|
| follow_id | string | The follow. The other follow tools take it. |
| platform | "youtube" | "tiktok" | Where the creator is. |
| creator_handle | string | The creator's @handle, or the channel's name for one without a handle. |
| creator_name | string | null | Their display name, when the platform gives one. |
| creator_avatar_url | string | null | Their picture, when the platform gives one. |
| creator_url | string | Their channel or profile. A YouTube channel's is always its /channel/UC… link. |
| rule | { description, hashtags, mentions, keywords, requires_disclosure } | The rule as saved: description is null when there is none, and each list is lowercased, without # or @. |
| rule_summary | string | The rule in short: its description, else its filters, such as #ad · "marathon". |
| rule_version | integer | Starts at 1 and goes up each time the rule changes. |
| question | string | What every matched video is asked. |
| max_duration_minutes | integer | The longest video a match is answered for. It depends on your plan and on the question. |
| notify | NotifyItem[] | Its channels, with URL targets masked. |
| status | "active" | "paused" | "needs_attention" | needs_attention after three failed checks in a row (see When a check fails on Follows). Wins over paused. |
| paused | boolean | Whether the follow is paused, even while status says needs_attention. |
| status_reason | string | null | Why it needs attention. |
| consecutive_failures | integer | Checks that failed in a row. 0 once one works. |
| last_error | string | null | Why the last check failed. null once one works. |
| last_error_at | string (ISO 8601) | null | When the last check failed. |
| failing_since | string (ISO 8601) | null | When the current run of failed checks began. |
| creator_gone | boolean | The platform says the creator was deleted. It is no longer checked and cannot be resumed. |
| last_polled_at | string (ISO 8601) | null | When it last checked for new videos. |
| last_matched_at | string (ISO 8601) | null | When a video last matched. |
| created_at | string (ISO 8601 timestamp) | When it was created. |
| weekly_cap | integer | How many videos this follow can analyze in any 7 days. |
| stats | { checked, matched, analyzed, this_week } | Videos checked, matched, and analyzed to completion, and the analyses counted against weekly_cap now. A failed or cancelled analysis does not count. |
A follow can be paused and need attention at once: status is what to show, paused where it returns once checks
work again.
The activity row
get_follow_activity returns one row per check:
| Prop | Type | Description |
|---|---|---|
| id | string | The check. Not a follow_id. |
| video_id | string | The platform's id for the video. |
| video_url | string | The video. |
| video_title | string | null | Its title. |
| video_thumbnail_url | string | null | Its thumbnail. |
| video_published_at | string (ISO 8601) | null | When it was posted. |
| verdict | "matched" | "rejected" | "skipped" | Whether it matched the rule. skipped appears only on older checks. |
| reason | string | null | One line on why. |
| skip_reason | string | null | Why a match was not analyzed. See below. |
| job_id | string | null | The analysis, once the video has one. Pass it to get_analysis. |
| rule_version | integer | The version of the rule that judged it. |
| created_at | string (ISO 8601 timestamp) | When it was checked. |
| job_status | "pending" | "processing" | "completed" | "failed" | "cancelled" | "held" | null | The job's status now. null when there is no job. |
| video_duration_seconds | integer | null | How long the video is, when known. |
| credits_charged | integer | null | What answering it cost: the analysis plus writing the answer. null until either is charged. |
A match that wasn't analyzed has a skip_reason. Once it is, its rows carry the job_id instead.
skip_reason | Why it was not analyzed | Next |
|---|---|---|
backfill | It was posted before you followed, or it matched only after a rule edit. Offered, never run alone | Run it from the console |
paused | The follow was paused | Run it from the console |
subscription_inactive | The subscription payment failed | Update the payment method, then run it |
capped | The follow had already analyzed its weekly_cap videos in the last 7 days | Not run |
skipped_duration | The video is longer than the plan allows for one job | Not run |
skipped_credits | The workspace could not cover the analysis when the match came in | Not run |
Offered matches are run from the console's Follows page; no tool runs them.
See also: the video MCP server: what you can ask, and how sign-in works.