# Node > Stream Moderation - Server Side SDKs - Node --- # Getting Started Stream's Moderation API lets you integrate content moderation into any application. This guide walks you through installing one of the [backend SDKs](https://getstream.io/docs/platform/backend-sdks/), creating a policy, and running your first moderation check. Moderation lives in the [Stream Dashboard](https://getstream.io/signin/?product=moderation), where you can create and tune policies, manage AI rules and blocklists, and review flagged content across your app. ## Start here - [Moderate any content](https://getstream.io/moderation/docs/node/integrations/custom-content/): Check text, images, audio, and video from your own product. - [Policies](https://getstream.io/moderation/docs/node/configuration/policies/): Choose which engines run and what happens when content matches. - [Stream Chat integration](https://getstream.io/moderation/docs/node/integrations/stream-chat/): Moderate chat messages with no code changes. - [Stream Feeds integration](https://getstream.io/moderation/docs/node/integrations/stream-feeds/): Automatically moderate activities and comments as they are created or updated. - [Review Queue](https://getstream.io/moderation/docs/node/content-moderation/review-queue/): Review flagged content and act on it: approve, delete, or ban. - [Live video and audio](https://getstream.io/moderation/docs/node/integrations/live-video-and-audio/): Moderate calls and livestreams in real time with keyframes and captions. ## Stream CLI and Agent Skills Manage moderation from your terminal with the [Stream CLI](https://getstream.io/cli/docs/), including scripting policy setup and AI-agent workflows. Install the CLI and the default [Agent Skills](https://getstream.io/agent-skills/docs/): ```bash curl -fsSL https://getstream.io/cli.sh | bash getstream skills ``` Then ask your agent in plain English, for example: ```text /stream Show flagged content in the review queue. ``` See the [CLI docs](https://getstream.io/cli/docs/) and [Agent Skills docs](https://getstream.io/agent-skills/docs/) for more. ## Installation ```bash npm install @stream-io/node-sdk # or yarn add @stream-io/node-sdk ``` ## Initialize the client ```js label="Node.js" import { StreamClient } from "@stream-io/node-sdk"; // or: const { StreamClient } = require("@stream-io/node-sdk"); const client = new StreamClient("YOUR_API_KEY", "YOUR_API_SECRET"); ``` ## Create a moderation policy Before checking content, create a policy that defines which rules to apply: ```js label="Node.js" await client.moderation.upsertConfig({ key: "my_config", ai_text_config: { rules: [ { label: "SPAM", action: "flag" }, { label: "HATRED", action: "remove" }, ], }, block_list_config: { rules: [{ name: "profanity_en", action: "remove" }], }, }); ``` > For Stream Chat, use config key `chat:messaging`. For Stream Feeds, use `feeds`. See [Configuration](https://getstream.io/moderation/docs/node/configuration/policies/) for details. ## Check content ```js label="Node.js" const response = await client.moderation.check({ entity_type: "post", entity_id: "post-123", entity_creator_id: "user-456", moderation_payload: { texts: ["Selling a bike, pickup in the city"], }, config_key: "my_config", }); console.log(response.recommended_action); // "keep", "flag", "remove", "shadow", or "bounce" ``` ## Handle the response The response includes: - `recommended_action` -- `"keep"`, `"flag"`, `"remove"`, `"shadow"`, `"bounce"`, `"bounce_flag"`, `"bounce_remove"`, `"mask"`, or `"mask_flag"` - `status` -- `"completed"` or `"pending"` (if async checks are still running) - `item` -- the review queue item, when the action is not `keep` The check returns a recommendation for your code to enforce. Stream adds flagged and removed content to the review queue: ```mermaid flowchart TD content[Your content] --> check[Check API] check --> rec{recommended_action} rec -->|keep| keep[Publish it] rec -->|flag| queue[Review queue] rec -->|shadow| hidden[Show it only to the author] rec -->|remove| removed[Do not publish it] rec -->|bounce| bounced[Reject it before it is published] hidden --> queue removed --> queue queue --> review[Moderator marks it reviewed, restores it, or bans the user] ``` ```js label="Node.js" if (response.recommended_action === "keep") { // Publish the content } else if (response.recommended_action === "flag") { // Publish it, and review the queued item console.log("Flagged for review:", response.item); } else if (response.recommended_action === "shadow") { // Show it only to the author } else if ( response.recommended_action === "remove" || response.recommended_action === "bounce" || response.recommended_action === "bounce_remove" ) { // Do not publish it. Removed items stay in the review queue. console.log("Not published:", response.item); } ``` ## What's next - [Policies](https://getstream.io/moderation/docs/node/configuration/policies/): Choose which engines run and what happens when content matches. - [Review queue](https://getstream.io/moderation/docs/node/content-moderation/review-queue/): Query and act on flagged content. - [Webhooks](https://getstream.io/moderation/docs/node/content-moderation/webhooks/): Get review queue events delivered to your backend. - [Client configuration](https://getstream.io/moderation/docs/node/guides/client-configuration/): HTTP defaults, timeouts, and connection pooling. ## FAQ **Which engines can check content?** LLM and NLP text engines, plus image, video and audio moderation. Start with the [LLM text engine](https://getstream.io/moderation/docs/node/engines/ai-llm-text/). **Do messages sent from my server get moderated?** Content sent to the Check API is always moderated. Chat messages sent from the server skip moderation by default; set `force_moderation` to `true` to include them. The flag is server-side only and ignored when sent from a client-side SDK. See [chat moderation](https://getstream.io/moderation/docs/node/integrations/stream-chat/#server-side-integration). **Can tenants manage their own moderation?** Yes, for chat. Tenants manage their own policies, with client-side access restricted to their team, and team blocklists need a feature flag from support. See [multitenancy](https://getstream.io/moderation/docs/node/configuration/multitenancy/). **Can I build my own review dashboard?** Yes, on the same APIs Stream's dashboard uses. The [dashboard guide](https://getstream.io/moderation/docs/node/guides/build-moderation-dashboard/) walks through it. --- For the most recent version of this documentation, visit [https://getstream.io/moderation/docs/node/](https://getstream.io/moderation/docs/node/). --- # Architecture & Benchmark Stream's Moderation API is designed to moderate text, images, audio, and video at high volume while maintaining low latency. Here we explain the architecture behind the API and share benchmarks from production traffic that show how it performs under load. ## Architecture overview The Moderation API integrates as a standalone platform for any app that has user-generated content. Stream Chat, Video, and Feeds are integrations on top of that same API. A few key things about the architecture: - **Edge network:** Your backend connects to the nearest Stream edge server. TLS termination, authentication, and rate limiting happen at the edge. - **Text, images, audio, and video:** One API and one policy cover all content types, so you do not need a separate vendor for each. - **Excellent performance:** English text typically completes in about 35 ms. High-volume text classification handles 1,500+ requests per second with API latency under 50 ms. Image and live video API calls remain within the same latency range. - **Highly redundant infrastructure:** Moderation runs primarily on Google Cloud, with failover across availability zones. ### Benchmark at a glance Stream Moderation is measured continuously on production traffic. As you can see in the chart, text classification reaches over 1,500 requests per second at the daily peak while API latency stays at about 50 ms or lower. Latency rises slightly as load increases because more text needs translation at peak times. It returns to about 35 ms overnight. There is no degradation as traffic moves through the day. ![Text classification load and latency](https://getstream.io/docs-assets/images/f3337767c2b5.png) Image and live-video traffic show the same pattern in the API: hundreds of requests per second, with latency between about 40 ms and 50 ms. Scoring a frame takes about 300 ms, so live-stream apps send frames without waiting for the model. ![Image and live-video load and latency](https://getstream.io/docs-assets/images/bc6cf7f70ff2.png) ## APIs and content types One policy applies across three APIs and five content types. | API | What you send | | --------------------------------- | ------------------------------------------------------------------------------------------------ | | `POST /api/v2/moderation/check` | Text, image, audio files, and video files from your own entities. Returns keep, flag, or remove. | | `POST /api/v2/moderation/labels` | High-volume text. Returns labels without creating a review item for every message. | | `POST /api/v2/moderation/analyze` | Images and live-video keyframes, with optional text. | | Content | How it runs | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Text** | Use check or labels. English text on the text send path typically completes in about 35 ms. | | **Images** | Use analyze or check. The API responds in about 40 ms. Scoring a photo takes about 300 ms. | | **Live video** | Your app samples keyframes and sends them to analyze. The same image model that scores still images scores the frames. You decide how often to sample. | | **Video files** | Use check. The API accepts the upload, and scoring runs in the background. | | **Audio files** | Use check. The file is transcribed and then run through the same text engines. analyze does not accept audio. | ## The tech behind the Stream Moderation API Stream uses Go as the backend language for moderation. ### Edge network When you call the API, the request goes to the nearest edge server. This is similar to how a CDN works, with more processing at the edge. Edge servers support HTTP/3 and HTTP/2. TLS terminates at the edge. Traffic between the edge and the origin servers is encrypted and uses long-lived HTTP/2 connections. Authentication, rate limiting, and CORS handling also run at the edge. For resilience, the edge infrastructure uses circuit breakers, request retries, consistent-hash routing, and locality-aware load balancing with failover to another availability zone when needed. ### API performance English text typically completes in about 35 ms, with a p95 of about 70 ms. Stream achieves this by caching policies and blocklists in memory and in Redis, and by keeping its own processing to a few milliseconds around the model call. Each API fits a different workload. The text send path calls check. Games and other high-volume text apps call labels. Images and live-video keyframes go to analyze. Uploaded video and audio files stay on check and finish in the background. ### Infra & testing A large Go integration test suite, production smoke tests, and QA run before each deploy. The infrastructure runs primarily on Google Cloud and is highly available. Enterprise customers can add a 99.999% uptime SLA. ## Benchmark We measure live production traffic, which includes the policies and models that customers actually run. The numbers therefore include real model time and do not come from a synthetic replay that hides it. The charts on this page cover a global snapshot of seven days of production traffic from all regions, including Europe, US East, and US West. English text on the text send path has a p50 of about 35 ms, a p95 of about 70 ms, and a p99 of about 100 ms. ![English text latency percentiles](https://getstream.io/docs-assets/images/90797df063bd.png) ### check: text (English) | | p50 | p95 | p99 | | -------------- | ------ | ------ | ------- | | Text send path | ~35 ms | ~70 ms | ~100 ms | ### labels: high-volume text | Load | p50 | | ------------ | ------- | | 1,500+ req/s | < 50 ms | ### analyze: images and live-video keyframes | Load | API p50 | Time to score a frame | | -------------------------- | ------- | --------------------- | | Hundreds / sec (peak ~700) | ~40 ms | ~300 ms | ### check: video files and audio files Both file types are accepted by the request and scored in the background. Video files go through a full-file job. Audio files are transcribed and then moderated as text. These paths are not inline, so we do not publish a millisecond p50 for them. **Key takeaways:** - **check** (English text): about 35 ms p50, 70 ms p95, 100 ms p99. - **labels**: 1,500+ requests per second at under 50 ms p50. - **analyze**: API under 50 ms, and a frame score in about 300 ms. - **Video files and audio files**: processed in the background on **check**. - API latency stays at about 50 ms or lower through the daily traffic peak. For model quality (precision and recall across content categories), see the [AI Moderation Benchmark Report](https://getstream.io/assets/stream-moderation-benchmark-17074005.pdf). This page is how the API is built and how it behaves under load. --- For the most recent version of this documentation, visit [https://getstream.io/moderation/docs/node/architecture-and-benchmark/](https://getstream.io/moderation/docs/node/architecture-and-benchmark/). --- # Roadmap & Changelog ## Roadmap - Dashboard control for which text engine runs on a policy. `llm_config` and `ai_text_config` already exist together on the API - JSON editor to set up the LLM by pasting a config - Policy versioning: revert to an older config from the policy or evaluation screen - Stronger scam and phishing detection (AI on top of URL allowlists, with user scoring) - LLM judge for sensitive labels - Vertical policy packs ## Changelog ### October 2026 **LLM context from custom data** - AI Text (LLM) policies can read selected `moderation_payload.custom` keys as background context, e.g. the article or video a comment is posted under and the earlier replies in its thread - Set `context_custom_keys` (up to 10 keys) and an optional `custom_keys_context_description` on `llm_config` - Only `texts` are moderated; the context is never scored, labelled or flagged, and instructions inside it are ignored - Details: [Context from custom data](https://getstream.io/moderation/docs/node/engines/ai-llm-text/#context-from-custom-data) ### September 2026 **Text normalization before moderation** - Chat, Feeds, and Check now decode HTML entities and strip zero-width / invisible characters **before** filters, AI, and the review queue run - Encoded URLs and words (`https://…`, missing semicolons, `&#104;` chains, `h​ttp`) match the same plain rules as the rendered text - Stored message bodies are unchanged; the review queue shows the decoded copy - Details: [Text Normalization](https://getstream.io/moderation/docs/node/guides/text-normalization/) **Analyze API** - New `analyze` method on the server-side SDKs (`POST /api/v2/moderation/analyze`) for per-field text and image screening - Documented on the Content page next to Check, with a comparison of when to use each - Named `texts` via the server-side SDKs. Raw `image: