Actions

Submit Action

submit_action applies a moderator's decision to a review queue item: delete content, ban a user, mark the item reviewed, restore content, and more.

Actions also run without a moderator. Each policy rule sets the action an engine applies when content matches, such as flag, remove, bounce, or mask. See Automatic actions.

POST /api/v2/moderation/submit_action

await client.moderation.submitAction({
  action_type: "mark_reviewed",
  item_id: "review_queue_item_id",
  user_id: moderatorId,
});

Request Parameters

Key Required Type Description
action_type true string The action to take. See supported action types below.
item_id false string Review queue item ID. Required for most actions.
user_id false string User ID of the moderator performing the action.
ban false object Ban-specific parameters (used with ban action type).
unban false object Unban-specific parameters (used with unban action type).
delete_message false object Delete message parameters.
delete_activity false object Delete activity parameters.
delete_poll false object Delete poll parameters: reason.
delete_poll_option false object Delete poll option parameters: reason, option_id.
delete_poll_answer false object Delete poll answer parameters: reason.
mark_reviewed false object Mark reviewed parameters: reason, decision_reason.
restore false object Restore parameters: reason, decision_reason.
unblock false object Unblock parameters. Deprecated. Use restore.
custom false object Custom action parameters (used with custom action type).
reject_appeal false object Reject appeal parameters.
appeal_id false string Appeal ID (for appeal-related actions).
decision_reason false string Not a top-level field. Set it inside restore, unban, mark_reviewed, or reject_appeal.

Response

Key Type Description
item object The updated review queue item.

Supported Action Types

Some actions only work on content stored in Stream Chat, Stream Feeds, or Stream Video. Actions marked Any also work for your own (standalone) entity types.

Action Type Works with Description
mark_reviewed Any Mark the item as reviewed without taking further action (content is safe).
flag Any Flag an entity from the review action. Custom entity types need server-side auth.
ban Any Ban the content creator. Supports shadow, IP, and temporary bans. Channel bans are Stream Chat only.
unban Any Unban a previously banned user. Also accepts appeals automatically.
bypass Any Turn moderation bypass on or off for the user.
escalate Any Escalate the item for higher-level review.
de_escalate Any De-escalate a previously escalated item.
reject_appeal Any Reject a user's appeal against a moderation decision.
custom Any Trigger a custom action (sends a webhook to your server). See Custom Action.
delete_message Stream Chat Delete a flagged chat message.
delete_user_messages Stream Chat Delete messages created by the user.
delete_poll Stream polls Delete the poll from the review item filed by its creator. See Moderate poll text.
delete_poll_option Stream polls Delete one option of a poll and keep the rest. The option must still hold the text that was checked.
delete_poll_answer Stream Chat Delete a free-text poll answer on a chat message. Refused when that text has changed, or the poll is closed.
delete_activity Stream Feeds Delete a flagged activity.
delete_comment Stream Feeds Delete a flagged comment.
delete_reaction Stream Feeds Delete a flagged reaction (Feeds v2).
restore Stream Chat, Feeds Restore previously deleted content. Also accepts appeals automatically.
unblock Stream Chat, Feeds Deprecated. Use restore.
shadow_block Stream Chat, Feeds Shadow-block content (hidden from others but visible to the creator).
unmask Stream Chat, Feeds Clear a mask applied to the content.
delete_user Stream users Delete the user who created the content.
kick_user Stream Video Remove a user from a call.
end_call Stream Video End an active video call.

Delete Message

Stream Chat only. Delete a flagged chat message from the review queue.

await client.moderation.submitAction({
  action_type: "delete_message",
  item_id: "review_queue_item_id",
  user_id: moderatorId,
});

Ban User

Ban the creator of flagged content. timeout is minutes. A channel ban (Stream Chat only) sets channel_cid and channel_ban_only: true. delete_messages is soft, pruning, or hard. Rule-builder bans are different: ban_options.duration is seconds.

A ban without channel_cid works for any user, including users of your own app. Stream records it on the user (banned, ban_expires) and sends a user.banned webhook. Stream Chat and Feeds enforce it for you. In your own app, read the ban state or handle the webhook and block the user yourself.

await client.moderation.submitAction({
  action_type: "ban",
  item_id: "review_queue_item_id",
  ban: {
    reason: "Repeated harassment",
    timeout: 1440,
    channel_cid: "messaging:general",
    channel_ban_only: true,
  },
  user_id: moderatorId,
});

Ban from Future Channels

Stream Chat only. Set ban_from_future_channels: true on a ban action to also auto-apply the ban to any new channels created later by the same user who created the channel the target is being banned from. This is useful when a community owner repeatedly creates new channels and you want a known offender kept out without re-banning them every time. The ban is applied automatically as the channel creator opens additional channels and remains in effect until removed via an unban with remove_future_channels_ban: true.

await client.moderation.submitAction({
  action_type: "ban",
  item_id: "review_queue_item_id",
  ban: {
    reason: "Repeated harassment",
    ban_from_future_channels: true,
  },
  user_id: moderatorId,
});

To remove a previously applied future-channels ban, submit an unban action with remove_future_channels_ban: true:

await client.moderation.submitAction({
  action_type: "unban",
  item_id: "review_queue_item_id",
  unban: {
    remove_future_channels_ban: true,
  },
  user_id: moderatorId,
});

Restore Content

Restore previously deleted content. This is typically used when content was incorrectly removed. If the item has an associated appeal, the appeal is automatically accepted.

Restore works for Stream Chat messages and Stream Feeds activities, comments, and reactions. Stream doesn't store standalone content, so it can't restore it.

await client.moderation.submitAction({
  action_type: "restore",
  item_id: "review_queue_item_id",
  restore: {
    reason: "Removed by mistake, context checked",
    decision_reason: "Content was incorrectly flagged",
  },
  user_id: moderatorId,
});

reason is an internal moderator note. decision_reason is shown to the user only when the item has an appeal. See Moderator notes.

Moderator notes

Attach a free-text note (reason) to an action to record why a moderator took it. This is how a custom moderation dashboard keeps moderator comments next to each decision. The field is optional, and requests without it behave as before.

Set reason inside the action's own object:

Action type Field
delete_message, delete_activity, delete_comment, delete_reaction, delete_poll, delete_poll_option, delete_poll_answer, delete_user, delete_user_messages <action>.reason
ban ban.reason
flag flag.reason
shadow_block shadow_block.reason
escalate escalate.reason (a category) plus escalate.notes
mark_reviewed mark_reviewed.reason
restore restore.reason

unban, unblock (deprecated), custom, kick_user, end_call, unmask, de_escalate and bypass don't take a note. reject_appeal takes only decision_reason.

await client.moderation.submitAction({
  action_type: "mark_reviewed",
  item_id: "review_queue_item_id",
  mark_reviewed: { reason: "Checked context, sarcasm between friends" },
  user_id: moderatorId,
});

There is no length limit on reason.

reason and decision_reason

mark_reviewed, restore and unban also accept decision_reason. The two fields are stored in different places:

Field Stored on Who sees it
reason The moderation action log Moderators (logs, review queue, webhooks)
decision_reason The appeal, only when the item has one (appeal_id) The user who appealed

When mark_reviewed accepts an appeal and the content is restored automatically, the action is logged as restore and keeps mark_reviewed.reason.

Read the note back

The note is returned wherever action logs are:

For escalate, reason is the escalation category; the free-text note is in custom.notes.

Bulk actions

POST /api/v2/moderation/bulk_submit_action takes one top-level reason and writes it to every action log the call creates. ban and delete_user fall back to ban.reason and delete_user.reason when the top-level one is empty. A bulk restore with nothing to reverse is logged as mark_reviewed and keeps the reason.

Custom Action

Trigger a custom action that sends a moderation.custom_action webhook to your server. Use this to integrate your own moderation workflows. The event carries action_id (the custom.id you sent), action_options, and the review_queue_item. For Stream Chat messages it also includes message.

await client.moderation.submitAction({
  action_type: "custom",
  item_id: "review_queue_item_id",
  custom: {
    id: "notify_user",
    options: { notification: "Your content has been reviewed" },
  },
  user_id: moderatorId,
});

Remove standalone content

Stream doesn't store your own content (a listing title, a profile bio), so the delete actions can't remove it. They only accept Stream Chat, Feeds, and Video entity types. To remove standalone content from the review queue:

  1. Send a custom action, for example with id: "delete_listing".
  2. Handle the moderation.custom_action webhook and delete the content in your own system, using review_queue_item.entity_id.
  3. Send mark_reviewed for the item so it leaves the queue.

Customize review queue buttons

The action buttons in the dashboard review queue are action_config records, one per button and entity type. Create or update one with POST /api/v2/moderation/action_config. List them with GET /api/v2/moderation/action_config, delete one with DELETE /api/v2/moderation/action_config/{id}, or send a batch with POST /api/v2/moderation/action_config/bulk and POST /api/v2/moderation/action_config/bulk_delete.

await client.moderation.upsertActionConfig({
  entity_type: "stream:chat:v1:message",
  order: 1,
  action: "delete_message",
  description: "Delete message",
});

entity_type and action are required. description is the button label. order is 0-100. Omit id to create a button; send id to update one. A button can use the custom action, which is how you add a button for your own entity types (for example, a "Delete listing" button that fires moderation.custom_action).

Automatic actions

Each rule in a policy sets the action an engine applies when content matches. These are not submit_action types. They run at moderation time, before a moderator sees anything.

Action What happens Review queue
keep Nothing. The content is published. No
flag The content is published and sent to moderators. Yes
remove The content is blocked. Other users never see it. Yes
shadow The content is stored but shown only to its author, who doesn't know it was hidden. Yes
bounce The content is returned to the sender to edit and is not stored. Sending the same content again bounces it again. No
bounce_flag Like bounce, but if the sender sends the same content again, it is published and flagged. On resend
bounce_remove Like bounce, but if the sender sends the same content again, it is removed. On resend
mask Matched words are replaced with asterisks and the content is published. Blocklist and regex rules only. No
mask_flag Like mask, and the content is also flagged so moderators can see the original. Yes

Stream Chat and Feeds apply these actions for you. With the Check API, Stream returns the action as recommended_action and your app applies it.

Bounce

Bounce gives the sender a chance to fix a message instead of sending it to moderators. A bounced message is returned with an explanation, the sender edits it, and the edited message is moderated again. Bounced messages are not stored and don't appear in the dashboard.

Use bounce_flag for borderline content: the sender can still send it unchanged, and a moderator reviews it. Use bounce_remove for content that should never be published.

Set a bounce action on AI Text, blocklist, or regex rules in the dashboard Policies page.

The Stream Chat UI SDKs show a warning next to a bounced message. Tapping it lets the sender edit, send anyway (if permitted), or delete the message. These SDK versions handle bounced messages out of the box:

Without a UI SDK, read the moderation object on the send message response. When action is bounce, show original_text back to the sender so they can edit it:

{
  "message": {
    "type": "error",
    "text": "The message original text",
    "user_id": "user_id",
    // other message data
    "moderation": {
      "action": "bounce",
      "original_text": "The message original text"
    }
  }
}

Mask

Mask hides sensitive text, such as phone numbers, while the content stays published. With mask_flag, readers see the masked text while moderators review the original.

For example, add a regex blocklist that matches phone numbers ((?m)(\+?\d{1,2}\s?)?(\(?\d{3}\)?[\s.-]?)?\d{3}[\s.-]?\d{4}) and set its action to mask_flag:

Original message Stored and shown to everyone
Text me at 555-123-4567 after 6pm Text me at ************ after 6pm

The moderator sees the original message in the review queue and can remove it, or use Unmask to restore the original text.

Mask works for Stream Chat messages and Stream Feeds v3 activities and comments. It is not supported for Feeds v2. Add the mask_flag rule to your policy's block_list_config:

await client.moderation.upsertConfig({
  key: "chat",
  block_list_config: {
    enabled: true,
    rules: [{ name: blocklistName, action: "mask_flag", team: "" }],
  },
});

For Feeds, use the same rule on the feeds policy (or feeds:{group_id} / feeds:{group_id}:{feed_id}). When an activity or comment matches the blocklist:

  • The stored text is replaced with the masked version, so every reader, including the author, sees the censored text.
  • The original text is returned in moderation.original_text in the response of the add or update call. Moderators can always see the original text in the review queue.
  • With mask_flag, the item is flagged (moderation_action: "flag" on the activity, moderation.action: "flag" on the comment) and shows up in the review queue.

Moderators can use the Unmask action in the review queue to restore the original text.