NetraFlow

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/mcp

Then 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 netraflow

Any 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_credits returns its tenant_id and name. You can't switch yet.
  • How long it lasts. You approve again every 30 days. Access ends when you leave the workspace.

Tools

ToolKindDescription
analyze_videosubmitSubmit a YouTube, TikTok or Instagram link for analysis. Returns a job_id.
check_statusreadA job's status and progress, without its results.
get_analysisreadA job with its results.
list_analysesreadYour jobs, newest first, with filters and pages.
cancel_jobwriteCancel a pending, processing or held job that is not in a batch.
create_batchsubmitSubmit up to 50 links with the same capabilities, and questions across all of them.
get_batchreadA batch's status, its jobs, and the answers across the set.
list_batchesreadYour batches, newest first, with a status filter and pages.
cancel_batchwriteCancel a batch and every job in it that has not ended.
check_creditsreadThe workspace, its plan, its credit balance and its limits.
get_usagereadCredit totals for a period and the transactions behind them.
follow_creatorwriteFollow a channel or profile and analyze new videos that match a rule.
list_followsreadEvery follow, with its rule, channels, counts and the plan's follow limits.
update_followwritePause or resume a follow, replace its rule, or replace its channels.
unfollow_creatorwriteDelete a follow. Its analyses are kept.
get_follow_activityreadThe 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:

CodeMeaning
FOLLOW_NOT_FOUNDNo follow with that follow_id in this workspace
FOLLOW_EXISTSThe workspace already follows this creator with the same rule and question. details.follow_id names that one
PLAN_REQUIREDSlack and webhook channels need Starter or above, or the plan's follow limit is used
CONFLICTThe 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.

PropTypeDescription
urlstringA YouTube, TikTok or Instagram video link.
capabilitiesstring[]At least one of: transcription, summary, brands, metadata, custom_query, objects.
queriesstring[]Your own questions about the video. Up to 10, 500 characters each, within your plan's limit.
brandsstring[]Brand names to report. Up to 50, 100 characters each.
surfacesstring[]Surface slugs to look on, such as "jersey". Up to 10.
objectsstring[]Things to track, in your own words. Up to 5, 100 characters each. Required by objects.
from_jobstringAn earlier job of yours on the same video. Charged the same either way.
languagestringLanguage hint for transcription, such as "en". Up to 10 characters.
fpsinteger (1-4)Frames looked at per second, clamped to your plan's max_fps. Defaults to 1.
webhook_urlstringAn https URL for job webhooks. Starter and above.
return_proofbooleanInclude the storyboard and speech_context. Defaults to true.
dry_runbooleanReturn the credit estimate and create nothing. Defaults to false.
max_creditsintegerFail 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.

PropTypeDescription
job_idstringThe 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.

PropTypeDescription
job_idstringThe 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.

PropTypeDescription
status"pending" | "processing" | "completed" | "failed" | "cancelled" | "held"Only jobs with this status.
follow_idstring | "any"Only the jobs this follow created, or with "any", every job a follow created.
limitinteger (1-100)Jobs per page. Defaults to 20.
cursorstringpagination.next_cursor from the previous page.
created_afterstring (ISO 8601 timestamp)Only jobs created after this time.
created_beforestring (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.

PropTypeDescription
job_idstringThe 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.

PropTypeDescription
urlsstring[]Video links. Up to 50, within your plan's batch size.
capabilitiesstring[]At least one of: transcription, summary, brands, metadata, custom_query, objects.
queriesstring[]Questions asked of every video. Up to 10, 500 characters each.
objectsstring[]Things to track in every video. Up to 5, 100 characters each.
fpsinteger (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_urlstringAn https URL for batch.completed and batch.paused. Starter and above.
return_proofbooleanInclude the storyboard and speech_context. Defaults to true.
dry_runbooleanReturn the per-video and total estimate and create nothing. Defaults to false.
max_creditsintegerRefuse 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.

PropTypeDescription
batch_idstringThe 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.

PropTypeDescription
status"processing" | "completed" | "failed" | "cancelled" | "paused"Only batches with this status.
limitinteger (1-100)Batches per page. Defaults to 20.
cursorstringpagination.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.

PropTypeDescription
batch_idstringThe 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.

PropTypeDescription
start_datestring (ISO 8601 timestamp)Period start. Defaults to the start of this month.
end_datestring (ISO 8601 timestamp)Period end. Defaults to now.
type"charge" | "purchase" | "refund" | "grant" | "forfeit"Only transactions of this type.
limitinteger (1-100)Transactions per page. Defaults to 50.
cursorstringpagination.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.

PropTypeDescription
urlstringA YouTube channel or TikTok profile link, such as https://www.youtube.com/@jawed. Not a video, and not Instagram.
rule_descriptionstringWhat 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.
hashtagsstring[]Exact hashtags. Up to 20, 100 characters each.
mentionsstring[]Exact @mentions. Up to 20, 100 characters each.
keywordsstring[]Whole words in the title or description. Up to 20, 100 characters each.
requires_disclosurebooleanOnly videos that disclose an ad or a paid partnership. Defaults to false.
questionstringWhat to find out from every matched video, in the user's words, such as "Who sponsored the video?". Up to 500 characters.
notifyNotifyItem[]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.

PropTypeDescription
follow_idstringThe follow to change.
status"active" | "paused"Paused keeps checking new videos but runs nothing.
rule_descriptionstringPart of the new rule. Up to 1,000 characters.
hashtagsstring[]Part of the new rule. Up to 20, 100 characters each.
mentionsstring[]Part of the new rule. Up to 20, 100 characters each.
keywordsstring[]Part of the new rule. Up to 20, 100 characters each.
requires_disclosurebooleanPart of the new rule.
questionstringThe new question. Matches from then on answer it; earlier answers stay. Up to 500 characters.
notifyNotifyItem[]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.

PropTypeDescription
follow_idstringThe 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.

PropTypeDescription
follow_idstringThe 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:

PropTypeDescription
type"slack" | "webhook" | "email"What kind of channel it is.
targetstringWhere it goes. The rules for each type are below.
  • slack: a hooks.slack.com incoming-webhook URL. Posts the video, why it matched and the answer.
  • webhook: an https:// URL on a public address. Receives the follow.match.completed event, 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.

PropTypeDescription
follow_idstringThe follow. The other follow tools take it.
platform"youtube" | "tiktok"Where the creator is.
creator_handlestringThe creator's @handle, or the channel's name for one without a handle.
creator_namestring | nullTheir display name, when the platform gives one.
creator_avatar_urlstring | nullTheir picture, when the platform gives one.
creator_urlstringTheir 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_summarystringThe rule in short: its description, else its filters, such as #ad · "marathon".
rule_versionintegerStarts at 1 and goes up each time the rule changes.
questionstringWhat every matched video is asked.
max_duration_minutesintegerThe longest video a match is answered for. It depends on your plan and on the question.
notifyNotifyItem[]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.
pausedbooleanWhether the follow is paused, even while status says needs_attention.
status_reasonstring | nullWhy it needs attention.
consecutive_failuresintegerChecks that failed in a row. 0 once one works.
last_errorstring | nullWhy the last check failed. null once one works.
last_error_atstring (ISO 8601) | nullWhen the last check failed.
failing_sincestring (ISO 8601) | nullWhen the current run of failed checks began.
creator_gonebooleanThe platform says the creator was deleted. It is no longer checked and cannot be resumed.
last_polled_atstring (ISO 8601) | nullWhen it last checked for new videos.
last_matched_atstring (ISO 8601) | nullWhen a video last matched.
created_atstring (ISO 8601 timestamp)When it was created.
weekly_capintegerHow 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:

PropTypeDescription
idstringThe check. Not a follow_id.
video_idstringThe platform's id for the video.
video_urlstringThe video.
video_titlestring | nullIts title.
video_thumbnail_urlstring | nullIts thumbnail.
video_published_atstring (ISO 8601) | nullWhen it was posted.
verdict"matched" | "rejected" | "skipped"Whether it matched the rule. skipped appears only on older checks.
reasonstring | nullOne line on why.
skip_reasonstring | nullWhy a match was not analyzed. See below.
job_idstring | nullThe analysis, once the video has one. Pass it to get_analysis.
rule_versionintegerThe version of the rule that judged it.
created_atstring (ISO 8601 timestamp)When it was checked.
job_status"pending" | "processing" | "completed" | "failed" | "cancelled" | "held" | nullThe job's status now. null when there is no job.
video_duration_secondsinteger | nullHow long the video is, when known.
credits_chargedinteger | nullWhat 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_reasonWhy it was not analyzedNext
backfillIt was posted before you followed, or it matched only after a rule edit. Offered, never run aloneRun it from the console
pausedThe follow was pausedRun it from the console
subscription_inactiveThe subscription payment failedUpdate the payment method, then run it
cappedThe follow had already analyzed its weekly_cap videos in the last 7 daysNot run
skipped_durationThe video is longer than the plan allows for one jobNot run
skipped_creditsThe workspace could not cover the analysis when the match came inNot 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.

On this page