Documentation

This list is provided byYoutube ProtectorYoutube ProtectorandOwners AllianceOwners Alliance
Overview

This guide covers three things:

  1. Public API setup — how to consume the read-only scammers endpoint.
  2. Discord moderation — block known scammers from your Discord server with ready-made bots in JavaScript and Python.
  3. Supabase Auth hook — block signups to your app when a Discord identity matches the scammer list.
1) Public API Setup

API Overview

Your application exposes a read-only REST API that provides access to the scammers database. This endpoint is designed for integration with Discord bots, web applications, and other services that need to check against known scammers.

Endpoint Details

GET /api/scammers

Returns a paginated list of all scammers in the database with their associated images and sources.

Authentication: None required
Content-Type: application/json
Rate Limiting: 100 requests/minute
Cache Headers: 5-minute cache

Data Structure

The API returns a JSON object with the following structure:

api-response.json
{  "scammers": [    {      "id": "uuid-v4-format",      "name": "Scammer Name",      "avatar_url": "https://example.com/avatar.jpg",      "discord_id": "123456789012345678",      "last_scam_location": "Berlin, Germany",      "last_scam_service": "Creators Market",      "scammer_images": [        {          "image_url": "https://example.com/evidence1.jpg",          "caption": "Screenshot of scam attempt"        }      ],      "scammer_sources": [        {          "source_name": "Discord Server Reports",          "source_url": "https://discord.com/channels/..."        }      ]    }  ]}

Field Descriptions

id (UUID)

Unique identifier for each scammer record

name (string)

The scammer's display name or username

avatar_url (string | null)

Profile picture URL if available

discord_id (string | null)

Discord user ID for cross-platform identification

last_scam_location (string | null)

Geographic location of last reported scam

last_scam_service (enum | null)

Platform where scam was reported (Discord, etc.)

scammer_images (array)

Evidence images and screenshots

scammer_sources (array)

Original reports and references

Usage Examples

Basic Fetch Example

basic-fetch.js
async function getScammers() {  try {    const response = await fetch('https://s0.renderdragon.org/api/scammers');        if (!response.ok) {      throw new Error('HTTP error! status: ' + response.status);      return [];    }        const data = await response.json();    return data.scammers;  } catch (error) {    console.error('Failed to fetch scammers:', error);    return [];  }}

Discord Bot Integration

discord-bot.js
// Cache scammer IDs for quick lookupconst scammerIds = new Set();async function updateScammerCache() {  const scammers = await getScammers();  scammerIds.clear();    scammers.forEach(scammer => {    if (scammer.discord_id) {      scammerIds.add(scammer.discord_id);    }  });    console.log('Loaded ' + scammerIds.size + ' scammer IDs');}// Check if user is a known scammerfunction isKnownScammer(userId) {  return scammerIds.has(userId);}

React Hook Example

useScammersData.ts
import { useState, useEffect } from 'react';function useScammersData() {  const [scammers, setScammers] = useState([]);  const [loading, setLoading] = useState(true);  const [error, setError] = useState(null);  useEffect(() => {    async function fetchScammers() {      try {        setLoading(true);        const response = await fetch('https://s0.renderdragon.org/api/scammers');        const data = await response.json();        setScammers(data.scammers);      } catch (err) {        setError(err.message);      } finally {        setLoading(false);      }    }        fetchScammers();  }, []);  return { scammers, loading, error };}

Error Handling

Common HTTP Status Codes:
  • 200 - Success
  • 429 - Rate limited (too many requests)
  • 500 - Internal server error
  • 503 - Service temporarily unavailable

Always implement proper error handling and consider implementing retry logic with exponential backoff.

Best Practices

  • Cache the response: Store the API response locally and refresh periodically rather than calling the API on every user check.
  • Handle rate limits: Implement proper backoff strategies if you exceed the rate limit.
  • Validate Discord IDs: Discord user IDs are 17-19 digit numbers. Always validate the format.
  • Consider privacy: Only store and process the data you actually need for your use case.
  • Monitor for updates: Set up periodic refresh to catch new scammer reports.
2) Block scammers on Discord

JavaScript (discord.js)

Fetch the public scammers list and ban accounts on member join. Add a refresh command to re-pull the list.

bot.ts
// package.json// {//   "type": "module",//   "dependencies": { "discord.js": "^14.14.1", "node-fetch": "^3.3.2" }// }import { Client, GatewayIntentBits, Events } from "discord.js";import fetch from "node-fetch";// 1) Environmentconst DISCORD_TOKEN = process.env.DISCORD_TOKEN!; // Bot tokenconst API_BASE = process.env.API_BASE_URL || "https://s0.renderdragon.org"; // your app base URL// 2) Cache scammers from your public APIasync function fetchScammerIds(): Promise<Set<string>> {  const res = await fetch(API_BASE + "/api/scammers");  if (!res.ok) throw new Error("Failed to fetch scammers");  const data = await res.json();  const ids = new Set<string>();  for (const s of data.scammers) {    if (s.discord_id) ids.add(String(s.discord_id));  }  return ids;}const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers] });let banned = new Set<string>();client.once(Events.ClientReady, async () => {  console.log("Logged in as " + (client.user?.tag ?? ""));  banned = await fetchScammerIds();  console.log("Loaded " + banned.size + " scammer IDs");});// 3) Prevent scammers from joiningclient.on(Events.GuildMemberAdd, async (member) => {  try {    if (banned.has(member.id)) {      await member.ban({ reason: "Listed as scammer" });      console.log("Banned " + member.user.tag);    }  } catch (e) {    console.error("Failed banning member", e);  }});// 4) Command to refresh cache (optional)client.on(Events.InteractionCreate, async (interaction) => {  if (!interaction.isChatInputCommand()) return;  if (interaction.commandName === "refresh_scamlist") {    banned = await fetchScammerIds();    await interaction.reply("Scam list refreshed. Entries: " + banned.size);  }});client.login(DISCORD_TOKEN);

Python (discord.py)

bot.py
# requirements.txt# discord.py==2.4.0# aiohttp==3.9.5import osimport asyncioimport aiohttpimport discordfrom discord.ext import commandsTOKEN = os.getenv("DISCORD_TOKEN")API_BASE = os.getenv("API_BASE_URL", "https://s0.renderdragon.org")intents = discord.Intents.default()intents.members = Truebot = commands.Bot(command_prefix="!", intents=intents)banned: set[str] = set()async def get_scammer_ids() -> set[str]:    async with aiohttp.ClientSession() as session:        async with session.get(f"{API_BASE}/api/scammers") as resp:            resp.raise_for_status()            data = await resp.json()            return {str(s.get("discord_id")) for s in data.get("scammers", []) if s.get("discord_id")}@bot.eventasync def on_ready():    global banned    print(f"Logged in as {bot.user}")    banned = await get_scammer_ids()    print(f"Loaded {len(banned)} scammer IDs")@bot.eventasync def on_member_join(member: discord.Member):    try:        if member.id and str(member.id) in banned:            await member.ban(reason="Listed as scammer")            print(f"Banned {member}")    except Exception as e:        print("Ban failed", e)@bot.command()@commands.has_permissions(administrator=True)async def refresh_scamlist(ctx: commands.Context):    global banned    banned = await get_scammer_ids()    await ctx.reply(f"Scam list refreshed. Entries: {len(banned)}")bot.run(TOKEN)

Tip: run your bot with a process manager (PM2 / systemd) and store `DISCORD_TOKEN` and `API_BASE_URL` in environment variables.

3) Supabase: Block Discord-based signups

Use the Before User Created Auth Hook to call an Edge Function that rejects signups when the incoming Discord provider_id is present in the public.scammers table. The function inspectsuser.identities first (recommended), then falls back to raw_user_meta_data.provider_id if present.

Edge Function

supabase/functions/before-user-created/index.ts
// supabase/functions/before-user-created/index.ts// Deploy as an HTTP Edge Function and configure as the Auth Hook: Before User Created// Supabase Dashboard -> Authentication -> Hooks -> before-user-created -> URL of this functionimport "jsr:@supabase/functions-js/edge-runtime.d.ts";import { createClient } from "https://esm.sh/@supabase/supabase-js@2";// Environment variables configured in the function settingsconst SUPABASE_URL = Deno.env.get("SUPABASE_URL")!;const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!; // service role requiredconst supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {  auth: { persistSession: false },});function extractDiscordId(payload: any): string | null {  // Priority 1: identity provider record  const identities = payload?.user?.identities ?? [];  for (const id of identities) {    if (id.identity_data?.provider === "discord" || id.provider === "discord") {      const pid = id.identity_data?.provider_id || id.provider_id;      if (pid) return String(pid);    }  }  // Priority 2: raw_user_meta_data.provider_id  const raw = payload?.user?.user_metadata || payload?.user?.raw_user_meta_data;  if (raw?.provider_id) return String(raw.provider_id);  return null;}Deno.serve(async (req: Request) => {  try {    const payload = await req.json();    const discordId = extractDiscordId(payload);    if (!discordId) {      // Allow signup if not a Discord signup      return new Response("{}", { headers: { "Content-Type": "application/json" } });    }    // Check if the discordId exists in our scammers table    const { data, error } = await supabase      .from("scammers")      .select("id")      .eq("discord_id", discordId)      .limit(1);    if (error) {      console.error("DB error", error);      // Fail-open or fail-closed? Choose fail-closed to be strict      return new Response(        JSON.stringify({ error: { http_code: 500, message: "Internal error" } }),        {          status: 200,          headers: { "Content-Type": "application/json" },        }      );    }    if (data && data.length > 0) {      // Block signup      return new Response(        JSON.stringify({ error: { http_code: 403, message: "Signup blocked due to scammer listing" } }),        { status: 200, headers: { "Content-Type": "application/json" } }      );    }    // Allow signup    return new Response("{}", { headers: { "Content-Type": "application/json" } });  } catch (e) {    console.error(e);    return new Response(      JSON.stringify({ error: { http_code: 400, message: "Bad request" } }),      {        status: 200,        headers: { "Content-Type": "application/json" },      }    );  }});
  • Edge Function must use the SERVICE_ROLE key because it runs before the user exists.
  • Consider logging the hook payload with PII redaction for auditing.
  • Keep the scammers table protected with RLS. The Edge Function bypasses RLS via service role.