Coming Soon

Appeal API

Overview

The Appeal API allows users to submit appeals against moderation decisions and enables moderators to review and decide on those appeals. Appeals can be submitted against review queue items (such as deleted messages, activities, or reactions) or against user bans (both global and channel-specific bans).

Submit Appeal

This endpoint allows users to submit an appeal against a moderation decision. You can appeal against:

  • Deleted Content
  • Blocked content
  • User Bans: Appeals against user bans (both global bans and channel-specific bans)

For each appeal, there must be an associated review queue item or a user ban. When appealing against content, only the user who created the content can submit an appeal. When appealing against a user ban, only the banned user can submit an appeal.

await client.moderation.appeal({
  appealReason: "I believe my message was wrongfully blocked",
  entityID: contentEntityId,
  entityType: "stream:chat:v1:message",
  attachments: [
    "imageurl",
  ],
}, { user_id: userId });

Request Parameters

keyrequiredtypedescription
entity_idtruestringUnique identifier of the entity being appealed. For user bans, this should be the user ID. For content, this should be the message/activity/reaction ID.
entity_typetruestringType of entity being appealed. Examples: stream:chat:v1:message, stream:feeds:v2:activity, stream:feeds:v2:reaction, stream:user (for user bans)
appeal_reasontruestringExplanation for why the content is being appealed. Maximum 500 characters.
attachmentsfalsearrayArray of attachment URLs (e.g., images) providing evidence for the appeal. Maximum 10 attachments.

Response

keytypedescription
appeal_idstringUnique identifier of the created appeal item.

Appeal Rules and Restrictions

Appeals Against Content (Messages, Activities, Reactions, Comments)

  • Only the user who created the entity can appeal against actions taken on it
  • You cannot appeal if the item is unreviewed and just flagged (unless the recommended action is “remove”)
  • You cannot appeal against shadow-banned content
  • You cannot appeal if the content is not deleted (for reviewed items)
  • You cannot appeal if an appeal already exists for the item

Appeals Against User Bans

  • Only the banned user can appeal against their own ban
  • The user must be banned (globally or in at least one channel) to submit an appeal
  • You cannot submit a new appeal if a submitted appeal already exists for the user

Example: Appeal Against User Ban

await client.moderation.appeal({
  appealReason: "Appeal to unban me",
  entityID: userId,
  entityType: "stream:user",
  attachments: [
    "imageurl",
  ],
}, { user_id: userId });

Query Appeals

This endpoint allows you to query appeals with filters, sorting, and pagination. When used from the client-side, querying appeals is automatically limited to appeals created by the authenticated user.

await client.moderation.queryAppeals({
  filter: {
    id: { $eq: "appeal_id" },
  },
  sort: [{ field: "created_at", direction: -1 }],
  limit: 20,
});

Request Parameters

keyrequiredtypedescription
filterfalseobjectFilter conditions for appeals (see below)
sortfalsearraySorting parameters for appeals
limitfalsenumberMaximum number of appeals to return
nextfalsestringCursor for pagination (from previous request)

Supported Filters

You can filter appeals by the following fields:

fieldtypedescription
idstringFilter by specific appeal ID
user_idstringFilter by user ID who created the appeal
entity_typestringFilter by entity type (e.g., stream:chat:v1:message)
entity_idstringFilter by entity ID
statusstringFilter by appeal status: submitted, accepted, rejected
created_atdatetimeFilter by creation date
updated_atdatetimeFilter by last update date

Response

keytypedescription
itemsarrayList of appeal items
nextstringNext cursor for pagination (if any)

Get Appeal

This endpoint allows you to retrieve a specific appeal item by its ID. When used from the client-side, users can only retrieve their own appeals.

await client.moderation.getAppeal("appealId");

Request Parameters

keyrequiredtypedescription
idtruestringUnique identifier of the appeal

Response

keytypedescription
itemobjectAppeal item with full details

The appeal item includes:

  • id: Appeal ID
  • user: User who created the appeal
  • entity_type: Type of entity being appealed
  • entity_id: ID of the entity being appealed
  • appeal_reason: Appeal Reason/explanation
  • status: Appeal status (submitted, accepted, rejected)
  • decision_reason: Reason provided by moderator (if decided)
  • attachments: Array of attachment URLs
  • created_at: When the appeal was created
  • updated_at: When the appeal was last updated

Moderation Actions and Appeals

The Submit Action endpoint can be used to accept or reject appeals when taking moderation actions. Most moderation actions automatically accept an appeal if one exists for the review queue item.

Accepting Appeals

The following actions automatically accept an appeal if one exists:

  • unban: Unbanning a user
  • mark_reviewed: Marking an item as reviewed
  • restore: Restoring deleted content
  • unblock: Unblocking blocked content

When performing these actions, you can optionally provide a decision_reason to explain why the appeal was accepted:

// Approve appeal while unbanning user
await client.moderation.submitAction({
  action_type: "unban",
  appeal_id: "appeal_id",
  unban: {
    decision_reason: "I am unbanning a user",
    // channel_cids: ["cid1", "cid2"], // optional: for channel-specific unbans
  },
}, { user_id: moderatorId });

// Approve appeal while unblocking content
await client.moderation.submitAction({
  action_type: "unblock",
  item_id: "item_id",
  unblock: {},
  decision_reason: "I am unblocking content",
}, { user_id: moderatorId });

Rejecting Appeals

To reject an appeal, use the reject_appeal action type:

await client.moderation.submitAction({
  action_type: "reject_appeal",
  appeal_id: "appeal_id",
  reject_appeal: {
    decision_reason: "I am rejecting your appeal",
  },
}, { user_id: moderatorId });

Request Parameters for Appeal Actions

keyrequiredtypedescription
action_typetruestringType of action: unban, restore, unblock, mark_reviewed, or reject_appeal
item_idfalsestringReview queue item ID (required for most actions, except reject_appeal and unban without review queue item)
appeal_idfalsestringAppeal ID (required for reject_appeal, optional for other actions - will be inferred from item_id if not provided)
decision_reasonfalsestringReason for the decision (shown to the user who submitted the appeal)
action_paramsfalseobjectAction-specific parameters (e.g., unban, restore, unblock, reject_appeal)

Appeal Status Values

Appeals can have the following statuses:

  • submitted: The appeal has been submitted and is awaiting review
  • accepted: The appeal has been accepted by a moderator
  • rejected: The appeal has been rejected by a moderator

The decided_by field is automatically populated with the moderator’s user ID when an appeal is accepted or rejected. This field is not shown to users but is tracked internally for audit purposes.

Filter by Appeal in Query Review Queue

The Query Review Queue endpoint supports filtering by appeal-related fields. This allows you to:

  • Filter review queue items that have appeals
  • Filter by appeal status
  • Filter by specific appeal ID

This is particularly useful for implementing moderation dashboards where you want to show items with pending appeals separately from other items.

Appeal Filters in Query Review Queue

filtertypedescription
appealbooleanFilter items that have an appeal (true) or don’t have an appeal (false)
appeal_statusstringFilter items by appeal status: submitted, accepted, rejected
appeal_idstringFilter items by specific appeal ID

Example: Query Review Queue Items with Appeals

// Query items with submitted appeals
await client.moderation.queryReviewQueue(
  {
    appeal: true,
    appeal_status: "submitted",
  },
  [{ field: "created_at", direction: -1 }],
  { limit: 20 }
);

// Query items with a specific appeal
await client.moderation.queryReviewQueue(
  {
    appeal_id: { $eq: "appeal_id" },
  },
  [{ field: "created_at", direction: -1 }],
  { limit: 20 }
);

Instead of moving review queue items from “Reviewed” to “Inbox” or vice versa, you can query review queue items with appeal status filters. This approach avoids potential problems that could arise from changing the status of review queue items and provides a more flexible way to organize your moderation workflow.

Complete Workflow Example

Here’s a complete example of the appeal workflow:

  1. User submits an appeal against deleted content:
const appealResponse = await client.moderation.appeal({
  appealReason: "This message was deleted by mistake. Please restore it.",
  entityID: "message_id",
  entityType: "stream:chat:v1:message",
  attachments: [],
}, { user_id: userId });

console.log("Appeal ID:", appealResponse.appeal_id);
  1. Moderator queries appeals to see pending appeals:
const appeals = await client.moderation.queryAppeals({
  filter: {
    status: "submitted",
  },
  sort: [{ field: "created_at", direction: -1 }],
  limit: 50,
});
  1. Moderator reviews the appeal and decides to accept it by restoring the content:
await client.moderation.submitAction({
  action_type: "restore",
  item_id: "review_queue_item_id",
  restore: {},
  decision_reason: "The content was deleted by mistake. Appeal accepted.",
}, { user_id: moderatorId });
  1. User queries their appeals to see the decision:
const myAppeals = await client.moderation.queryAppeals({
  filter: {},
  sort: [{ field: "created_at", direction: -1 }],
  limit: 20,
}, { user_id: userId });
  1. User gets specific appeal details:
const appeal = await client.moderation.getAppeal(appealResponse.appeal_id, { user_id: userId });
console.log("Appeal status:", appeal.item.status);
console.log("Decision reason:", appeal.item.decision_reason);

Best Practices

  1. Appeal Reason: Provide clear and concise explanations in the appeal text (max 500 characters). This helps moderators understand the context quickly.

  2. Attachments: Use attachments to provide evidence supporting your appeal (e.g., screenshots, context about why content was flagged incorrectly).

  3. Decision Reasons: When moderators accept or reject appeals, always provide a decision_reason to help users understand the decision.

  4. Filtering: Use appeal status filters in Query Review Queue to organize your moderation dashboard effectively, separating items with pending appeals from other items.

  5. Client-Side Restrictions: Remember that client-side requests automatically filter appeals to only show appeals created by the authenticated user. Use server-side requests for moderator dashboards that need to see all appeals.

© Getstream.io, Inc. All Rights Reserved.