How to fix an App Store Guideline 1.2 user-generated content rejection
The four safeguards Apple asks for, what reviewers have asked for in recent rejections, code for the filtering part, and a reply you can adapt for App Review.
Apple rejects an app under Guideline 1.2 when people can post content in it and any of four safeguards is missing. The four are a way to filter objectionable material before it is posted, a way to report offensive content with timely responses, a way to block abusive users, and published contact information. Build all four, then tell App Review (the Apple team that reviews each app) where each one is. A moderation API can do the filtering; the other three are features you build.
What Guideline 1.2 requires
Guideline 1.2 covers "apps with user-generated content or social networking services". User-generated content (UGC) is anything one user posts that other users can see: posts, comments, reviews, chat messages, usernames, bios and photos. Apple's text, as read on 16 Oct 2026:
To prevent abuse, apps with user-generated content or social networking services must include:
- A method for filtering objectionable material from being posted to the app
- A mechanism to report offensive content and timely responses to concerns
- The ability to block abusive users from the service
- Published contact information so users can easily reach you
The guideline adds: "It is your responsibility to remove content that violates this guideline, your terms of service, or your community standards." Two related rules reach further:
- Creator content (1.2.1). Content that a community of "creators" makes in your app, such as video, articles, audio or casual games, is treated as user-generated content. Creator apps must also let users identify content that exceeds the app's age rating, and use "an age restriction mechanism based on verified or declared age" (1.2.1(a)).
- Mini apps, chatbots and plug-ins (4.7.1). Software your app offers that is not embedded in its binary (Apple lists HTML5 and JavaScript mini apps and mini games, streaming games, chatbots and plug-ins) must "include a method for filtering objectionable material, a mechanism to report content and timely responses to concerns, and the ability to block abusive users".
| Safeguard | Apple's words | What to build | Can a moderation API do it? |
|---|---|---|---|
| Filtering | "A method for filtering objectionable material from being posted to the app" | A check on your server of every post, comment, profile and image before other users see it. Flagged content is rejected or held for a person | Yes. The API says which flags the text or image raises; your server rejects or holds it |
| Reporting | "A mechanism to report offensive content and timely responses to concerns" | A Report option on every post, comment, message and profile, a review queue, and a person who works through it every day | In part. A moderator can open the post's check in the dashboard's Logs and take one of your actions; the moderation.action webhook tells your server. The Report button, the queue of reports and the people are yours |
| Blocking | "The ability to block abusive users from the service" | A Block option that hides the blocked user's posts and messages from the person who blocked them | No |
| Contact | "Published contact information so users can easily reach you" | A support address inside the app and on the page your Support URL points to | No |
What reviewers asked for in recent rejections
Recent rejections are headed "Guideline 1.2 - Safety - User-Generated Content" and list what the reviewer found missing. Developers have posted these messages on Apple's Developer Forums. The lines below are App Review's words as those developers quoted them, not the published guideline, and they ask for more than its four points. The 2017 message and the first November 2025 message start with an EULA (end-user licence agreement: the terms each user accepts) that rules out objectionable content and abusive users.
| Forum thread | Posted | What the message asked for |
|---|---|---|
| 78288 | May 2017 | Terms (EULA) that "make it clear that there is no tolerance for objectionable content or abusive users"; filtering; a way for users to flag content; blocking; acting on reports "within 24 hours by removing the content and ejecting the user who provided the offending content" |
| 688227 | August 2021 | Filtering, flagging, and the same 24-hour line |
| 805586 | October 2025 | Only "A mechanism for users to block abusive users" |
| 807358 | November 2025 | The same five points as in 2017. After a resubmission, the developer listed three new points: "Age rating must reflect 18+", "A mechanism for users to immediately remove posts from the feed", and contact information "in the app itself" |
Two things follow:
- The 24-hour line is not in Guideline 1.2, which asks for "timely responses to concerns". Reviewers have used it since at least 2017, so plan to act on every report within a day.
- A rejection names only what the reviewer found missing. The October 2025 message asked for blocking alone, and the second rejection in thread 807358 added three points the first did not list. Fix every listed point, then check that the other safeguards work too.
How the pieces fit together
The flow below uses the Detectivision API for the checks. The call and event names are the ones in its docs.
| Step | What happens | Who does it | Call or event |
|---|---|---|---|
| 1. Post | A user posts. Your server checks the text and each image before anyone else can see them | Your server and Detectivision | POST /api/v1/moderation with "mode": "detect" |
| 2. Decide | Your server publishes the post, holds it for a moderator, or rejects it, by the flags raised | Your server | flagged, flags and status in the answer |
| 3. Report | A user reports a post, a message or a profile | Your app | your own report feature |
| 4. Review | A moderator looks at held and reported items and picks one of your actions | Your moderators, in the dashboard's Logs or your admin tool | POST /api/v1/moderation/{id}/actions |
| 5. Act | Your server removes the content and closes the account that posted it | Your server | the moderation.action webhook |
| 6. Block | A user blocks another, and the blocked user's content disappears for them | Your app and server | none |
The fix, step by step
1. Filter text and images before they are posted
Run the check on your server, between "the user pressed Post" and "other users can see it". This is pre-moderation; types of content moderation compares it with the other approaches. Keep the API key on the server: a key inside an app can be read by anyone, who can then spend your credits (quickstart).
- Text. Send it as the user wrote it: posts, comments, chat messages, usernames and bios.
- Images. Upload to private storage first, send the API a signed URL (it works while it is valid), and make the image public only after the check (image moderation).
- Mode. Use
"mode": "detect". It returns the flags raised and their confidence, and it never costs more than the other modes. Today's flags judge the whole text or image: the answer says that something was found, not where. So the filter rejects or holds the whole post; it cannot hide one word or one part of a picture.
On 16 Oct 2026 the public status endpoint listed these text flags: bad_habits, hate, insult, profanity, racism, self_harm, sexism and sexual; and these image flags: hate, scam, sexual and violence. Your own account's list comes from GET /api/v1/flags (flags). Toxicity detection and NSFW detection show how the text flags and the sexual flag are used.
Example response (format from the API reference; values illustrative):
curl https://api.detectivision.ai/api/v1/moderation \
-H "Api-Key: $DETECTIVISION_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "text_moderation",
"input": "Nobody wants you here. Delete your account, idiot.",
"mode": "detect",
"metadata": { "postId": "p_20931", "userId": "u_4417" }
}'{
"id": 58213,
"type": "text_moderation",
"mode": "detect",
"status": "completed",
"flagged": true,
"flags": [{ "flag": "insult", "confidence": 0.94 }],
"checked": [
"bad_habits",
"hate",
"insult",
"profanity",
"racism",
"self_harm",
"sexism",
"sexual"
],
"unchecked": [],
"tookMs": 640,
"usage": { "charged": 2, "remaining": 8731 },
"metadata": { "postId": "p_20931", "userId": "u_4417" }
}flagged is the verdict. checked lists every flag that was looked for; status is completed when all of them were. metadata comes back unchanged, so the result names the post it belongs to. tookMs and the other numbers are illustrative, not measurements.
The same check as a pre-post filter for a whole post, with its text and photos. Each request matches the API reference:
// On your server: the app sends the new post here, and the API key stays here.
const API = "https://api.detectivision.ai/api/v1/moderation";
// Your rules. This is an example: decide your own, flag by flag.
const REJECT = new Set(["hate", "racism", "sexism", "sexual", "violence"]);
const HOLD = new Set(["insult", "profanity", "bad_habits", "self_harm", "scam"]);
async function check(type, input, metadata) {
try {
const res = await fetch(API, {
method: "POST",
headers: { "Api-Key": process.env.DETECTIVISION_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ type, input, mode: "detect", metadata }),
signal: AbortSignal.timeout(30_000),
});
return res.ok ? await res.json() : null; // errors are never charged
} catch {
return null; // network error or timeout
}
}
// Returns "publish", "hold" (a moderator decides) or "reject".
export async function screenPost(post) {
const metadata = { postId: post.id, userId: post.userId };
const checks = post.imageUrls.map((url) => check("image_moderation", url, metadata));
if (post.text.trim()) checks.push(check("text_moderation", post.text, metadata));
let verdict = "publish";
for (const result of await Promise.all(checks)) {
if (!result || result.status !== "completed") {
verdict = "hold"; // not fully checked: never publish it unseen
continue;
}
for (const { flag } of result.flags) {
if (REJECT.has(flag)) return "reject";
if (HOLD.has(flag)) verdict = "hold";
}
}
return verdict;
}
import os
import requests
API = "https://api.detectivision.ai/api/v1/moderation"
# Your rules. This is an example: decide your own, flag by flag.
REJECT = {"hate", "racism", "sexism", "sexual", "violence"}
HOLD = {"insult", "profanity", "bad_habits", "self_harm", "scam"}
def check(type_, input_, metadata):
try:
r = requests.post(
API,
headers={"Api-Key": os.environ["DETECTIVISION_API_KEY"]},
json={"type": type_, "input": input_, "mode": "detect", "metadata": metadata},
timeout=30,
)
except requests.RequestException:
return None # network error or timeout
return r.json() if r.ok else None # errors are never charged
def screen_post(post):
"""Return "publish", "hold" (a moderator decides) or "reject"."""
metadata = {"postId": post["id"], "userId": post["user_id"]}
checks = [("image_moderation", url) for url in post["image_urls"]]
if post["text"].strip():
checks.append(("text_moderation", post["text"]))
verdict = "publish"
for type_, input_ in checks:
result = check(type_, input_, metadata)
if result is None or result["status"] != "completed":
verdict = "hold" # not fully checked: never publish it unseen
continue
raised = {f["flag"] for f in result["flags"]}
if raised & REJECT:
return "reject"
if raised & HOLD:
verdict = "hold"
return verdict
- The two sets are your policy. Which flags reject a post and which hold it is your decision; the code shows one choice.
self_harmis held, not rejected, so a person can reach out to the user. - Never publish unseen. An error, a timeout or a
partialresult (a flag that could not be checked right now) holds the post. Errors and partial results cost nothing (errors). - Act above your own line. A flag is raised from a confidence of 0.5, and findings below that never come back. What you can choose is what each confidence leads to: for example, hold flags under 0.7 for a person and reject above it.
- Languages. Text checks are reliable in English, Spanish, Portuguese, French, German, Italian, Turkish, Arabic, Russian and Hindi. Text in other languages is checked, but the results are not reliable (text moderation). If your users write in other languages, reports and human review carry more of the load.
- Posts that can wait. Save the post as pending, send the check with
"async": true, and decide when the result arrives by webhook; keep image URLs valid until then. It costs half of an answer your app waits for (async requests). - Video, audio and live streams. These checks are coming soon. Until then, hold uploaded videos for a person.
For the flags that fit each kind of app, see online communities, dating apps and marketplaces.
2. Add a report button and a review queue
Put a Report option on every post, comment, message and profile. Save who reported what and when. Let users hide any post from their own feed at once, and hide what they report: the second rejection in thread 807358 listed "a mechanism for users to immediately remove posts from the feed".
Then a person has to act on reports, quickly. With Detectivision:
- Logs. A moderator can open a check in the dashboard's Logs and see its flags, your
metadataand your actions. Store the check'sidwith the post, so a report leads straight to it. - Your actions. Define your own actions in Logs, such as Remove post and Ban user; an account can have up to 20. A moderator takes one in Logs, or your admin tool calls
POST /api/v1/moderation/{id}/actions. - The webhook. Each action sends a
moderation.actionwebhook to your endpoint (subscribe it to that event on the dashboard's Webhooks page), and your server carries the action out. Detectivision never removes content or bans anyone itself (metadata and actions). - Alerts. Subscribe to
moderation.flaggedto tell a moderator when a check raised a flag (webhooks).
The receiving end in Node.js, with the Standard Webhooks library the docs point to (npm install standardwebhooks express). remove and ban stand for the action keys you set up in Logs:
import express from "express";
import { Webhook } from "standardwebhooks";
const app = express();
const webhook = new Webhook(process.env.DETECTIVISION_WEBHOOK_SECRET); // whsec_...
const done = new Set(); // in production, keep these ids in your database
app.post("/webhooks/detectivision", express.raw({ type: "application/json" }), (req, res) => {
let event;
try {
event = webhook.verify(req.body, req.headers); // signature and timestamp
} catch {
return res.status(400).end();
}
res.status(204).end(); // answer at once, then do the work
const id = req.headers["webhook-id"]; // the same on every retry
if (done.has(id) || event.type !== "moderation.action") return;
done.add(id);
const { action, item } = event.data;
const { postId, userId } = item.metadata ?? {};
if (action.key === "remove" && postId) removePost(postId); // your own functions
if (action.key === "ban" && userId) ejectUser(userId);
});
The handler checks the signature on the raw body before it trusts the message. It answers well within the 10 seconds the docs allow, and it skips retries of a message it has already handled. Taking actions and sending their webhooks costs no credits.
3. Let users block each other
Add Block to every profile and every chat. Once a user blocks someone, hide that person's posts, comments and messages from them at once, and stop new messages between the two. Enforce it on your server, so it holds on every device, and let users undo it in their settings. A moderation API plays no part here: blocking is your feature.
4. Publish contact details and make users accept your terms
- Contact. Put a support email address or a contact form inside the app, for example under Settings, and on the page your Support URL points to. Guideline 1.5 asks that "your app and its Support URL include an easy way to contact you", and the second rejection in thread 807358 listed contact information "in the app itself".
- Terms. Show your terms before a user can post, and require acceptance. Reviewers' messages ask that the terms "make it clear that there is no tolerance for objectionable content or abusive users". Say what happens to people who break them: their content is removed and their account is closed.
- Age rating. Set an age rating that matches what users can post. The second rejection in thread 807358 listed "Age rating must reflect 18+". Creator apps also need the age restriction that 1.2.1(a) describes.
What to write to App Review
Reply in App Store Connect. Apple's guidelines say that if your app was rejected and you "would like to provide additional information", you should "use App Store Connect to communicate directly with the App Review team". Describe each safeguard and where the reviewer can find it. Give the reviewer a demo account too. Guideline 2.1 asks you to "include demo account info (and turn on your back-end service!) if your app includes a login", and the rejection quoted in thread 807358 said that a demo video "is not sufficient".
Write only what the build does today. Fill in the brackets with what is true for your app:
Hello,
Thank you for the review. Version [1.4.0] (build [58]) adds the safeguards
that Guideline 1.2 asks for. Each one can be tried with the demo account
below.
1. Filtering: every [post, comment, profile and photo] is checked on our
server before other users can see it. Flagged content is [rejected /
held until a moderator reviews it]. To try it: [steps].
2. Reporting: every [post, comment, message and profile] has a Report
option ([where to find it]). Reports go to our moderation queue, which
[who] reviews [how often]. [What happens next, for example: content
that breaks our terms is removed, and the account that posted it is
closed.]
3. Blocking: every [profile and chat] has a Block option ([where to find
it]). The blocked user's posts and messages disappear at once for the
user who blocked them.
4. Contact: [Settings > Contact us] in the app, and [support URL].
5. Terms: new users must accept our Terms of Use before they can post.
The terms say there is no tolerance for objectionable content or
abusive users.
Demo account: [user name] / [password]
[Your name]
If you disagree with the outcome, Apple's guidelines point to an appeal.
Google Play asks for the same, and a little more
Google Play's User Generated Content policy covers apps that contain or feature UGC, including specialized browsers or clients that direct users to a UGC platform. It defines UGC as "content that users contribute to an app, and which is visible to or accessible by at least a subset of the app's users". Google asks those apps for moderation that is effective and ongoing, and spells out what that means:
| Google Play asks for | In Apple's 1.2? | What to build |
|---|---|---|
| Users accept the app's terms of use or user policy "before users can create or upload UGC" | Not in the guideline text; App Review's messages ask for an EULA | A terms screen before the first post (step 4) |
| Terms that define objectionable content and behaviours, and prohibit them | Not in the guideline text; the EULA reviewers ask for covers it | A clear list in your terms |
| UGC moderation "as is reasonable and consistent with the type of UGC hosted by the app", with in-app reporting and blocking, and action against content or users | Yes: filtering, reporting and blocking | Steps 1 to 3 |
| In-app reporting of content and users, even in apps for a closed group such as one school or company | Yes: reporting | Step 2 |
| In-app blocking for one-to-one features such as direct messages, tagging and mentions | Yes: blocking | Step 3 |
| Reporting of users and content, and blocking of users, in apps with publicly accessible UGC such as social and blogger apps | Yes | Steps 2 and 3 |
| Safeguards so that in-app monetization does not encourage objectionable behaviour | No | No paid features that reward objectionable posts |
| If incidental sexual content can appear: hidden by default behind filters that take at least two user actions to turn off, children kept out with an age screen, and accurate answers to the content rating questionnaire | Apple has its own rule for UGC from a web-based service: mature content hidden by default and turned on only via your website | A setting, an age screen, and honest rating answers |
| Moderation of augmented reality (AR) content, and of where it is anchored | No | Only if your app has AR |
Apps "whose primary purpose is featuring objectionable UGC" are removed from Google Play, and so are apps that end up used mainly for it. The filtering in step 1 serves both stores; the other rows are features you build once.
Kids Category apps: ask App Review first
The Kids Category is the App Store category for apps designed for children. Guideline 1.3 says: "Kids Category apps may not send personally identifiable information or device information to third parties." Guideline 5.1.4 counts "photos, videos, drawings, the ability to chat" as personal information: an app that collects, transmits or can share it from a minor must have a privacy policy and follow children's privacy laws (both as read on 16 Oct 2026).
A moderation API is a third party: checking a child's message or photo means sending it to an outside service. If your app is in the Kids Category, ask App Review before you send children's content to any outside service, ours included. We cannot tell you how App Review will answer.
What this does not do
What a moderation API, ours included, will not do for you:
- Get your app through review. App Review decides. The API covers one safeguard out of four.
- Build the rest. Report and block buttons, contact details and terms are your features.
- Review reports. Detectivision gives you Logs, your own actions and webhooks, not moderators. A person on your side works the queue.
- Act on your platform. It never removes a post or closes an account. Your server does, when the
moderation.actionwebhook arrives. - Mark or mask part of a post. Today's flags judge the whole text or image. There are no word positions, no boxes, and no masked or blurred copies.
- Check every language equally. Text checks are reliable in English, Spanish, Portuguese, French, German, Italian, Turkish, Arabic, Russian and Hindi only.
- Check video, audio or live streams. Those checks are coming soon.
- Report what scores below 0.5. Findings under a flag's threshold never come back.
- Verify ages or match known abuse images. Detectivision does no age verification and no hash matching.
- Make any kind of app acceptable. Apple says apps used primarily for "pornographic content, Chatroulette-style experiences, random or anonymous chat, objectification of real people (e.g. 'hot-or-not' voting), making physical threats, or bullying" do not belong on the App Store. A filter does not change that.
Plans and the free trial are on the pricing page.
Or read the quickstart first: an API key and your first check, in a few steps.
Frequently asked
Will a moderation API get my app through App Review?
No tool can promise that; App Review decides. A moderation API covers one of the four safeguards, the filtering. The report and block features, the contact details and the terms reviewers ask for are yours to build, and your reply to App Review should say where each one is.
Does Apple require AI moderation?
No. Guideline 1.2 asks for "a method for filtering objectionable material from being posted to the app" and does not say how. A word list, a person who checks each post before it goes live and an API are all ways to filter. Choose the one that keeps up with your volume and your users' languages.
Do I need to check images too?
If users can post images, yes. The guideline speaks of objectionable material, not only text, and a filter that reads captions misses what is in the picture. The image flags are hate, scam, sexual and violence (status, 16 Oct 2026). Video checks are coming soon.
Do I have to act on reports within 24 hours?
Guideline 1.2 asks for "timely responses to concerns" and gives no number. The 24 hours comes from App Review rejection messages that developers posted on Apple's forums in 2017, 2021 and 2025, which ask the developer to remove the content and eject the user within 24 hours. Plan for a person to work the queue every day.
How many credits does pre-post filtering use?
Text is counted in units of 2,500 characters, where a started unit counts, and each image is one unit. The price table on the Credits page of the docs gives the credits per unit for each mode; detect never costs more than the others. That table is the price of a result sent later by webhook; an answer your app waits for costs twice as much. Illustrative: 300 posts a day, each with a short text and one photo, make 300 text units and 300 image units a day.
Can a post go live first and be checked afterwards?
Apple's words are "filtering objectionable material from being posted", so check before other users can see a post. If a post can wait, save it as pending, send the check with "async": true and decide when the moderation.completed webhook arrives. That costs half as much as an answer your app waits for.
Moderate forum posts, comments, usernames and avatars for insults, hate, sexual content and self-harm, in ten languages, with one API.
Check profile photos, bios and chat messages on your dating app for nudity, harassment, hate and scams, in ten languages, with one API.
Check listing photos, titles, descriptions, reviews and buyer messages for nudity, hate, violence and scam images, with one API.
Check comments and chat messages for insults, hate, harassment and self-harm in ten languages, with a flag for each kind of toxicity.
Detect nudity and sexual content in images, and sexual language in text written in ten languages, with one API call and a confidence for each finding.
- App Review Guidelines: 1.2 User-Generated Content, 1.2.1 Creator Content, 1.3 Kids Category, 1.5 Developer Information, 2.1 App Completeness, 4.7.1, 5.1.4 Kids (page last updated 8 June 2026)
- Guideline 1.2 - Safety - User-Generated Content (Apple Developer Forums thread 807358, rejection text posted by a developer, November 2025)
- Unreasonable Rejection (Apple Developer Forums thread 805586, rejection text posted by a developer, October 2025)
- Guideline 1.2 - Safety - User Generated Content (Apple Developer Forums thread 688227, rejection text posted by a developer, August 2021)
- iOS App rejected: 1.2 Safety: User Generated Content (Apple Developer Forums thread 78288, rejection text posted by a developer, May 2017)
- User Generated Content (Play Console Help, Google Play Developer Program Policy)
- Detectivision API status (GET /api/v1/status)
- Metadata and actions
- Webhooks
- Credits and billing
- Flags
- Text moderation
- Image moderation