SafeAdd API Documentation

The SafeAdd API gives you read-only access to community-sourced safety signals for Roblox users. Use it to add trust and reputation context to your tools, games, bots, and platforms.

Base URL
https://api.safeadd.io/v1

To get started, you need an API key. Create an account at api.safeadd.io, then generate your key from the dashboard. Every account starts with 100 free requests per month.

Authentication

All API requests must include your API key in the Authorization header using the Bearer scheme.

Header format
Authorization: Bearer sk_live_your_api_key

API keys start with sk_live_ followed by 64 hex characters. Keep your key secret — it grants access to your account's request quota. If your key is compromised, regenerate it from the dashboard. The old key is immediately revoked.

Requests without a valid key return 401 Unauthorized.

Profile Lookup

GET/v1/profile/{username}

Look up a Roblox user's safety summary by their Roblox username. Returns the community signal status, review distribution, flagged behaviors, and time-shape flags.

The username is case-insensitive. It must be 3–20 characters, alphanumeric or underscores only.

Example request
curl https://api.safeadd.io/v1/profile/Builderman \
  -H "Authorization: Bearer sk_live_your_api_key"
Example response (200 OK)
{
  "username": "Builderman",
  "robloxId": "156",
  "status": "Mostly positive",
  "flags": [],
  "reviewCounts": {
    "comfortable": 14,
    "neutral": 3,
    "uncomfortable": 0,
    "veryUncomfortable": 0
  },
  "behaviorTags": [],
  "reportCount": 17,
  "updatedAt": "2026-04-21T10:00:00Z"
}

If the user has not been reviewed on SafeAdd yet, the endpoint returns a valid response with status: "Not enough data" and zero counts — not a 404. This still counts as one request toward your quota.

Unknown user response
{
  "username": "SomeNewPlayer",
  "robloxId": null,
  "status": "Not enough data",
  "flags": [],
  "reviewCounts": {
    "comfortable": 0,
    "neutral": 0,
    "uncomfortable": 0,
    "veryUncomfortable": 0
  },
  "behaviorTags": [],
  "reportCount": 0,
  "updatedAt": null
}

Response Format

All responses are JSON. The profile lookup endpoint returns these fields:

FieldTypeDescription
usernamestringThe Roblox username as stored in SafeAdd.
robloxIdstring | nullThe Roblox user ID. Null if the user hasn't been indexed.
statusstringThe public safety label. See Status Labels below.
flagsstring[]Time-shape flags indicating recent patterns.
reviewCountsobjectBreakdown of reviews by comfort level.
behaviorTagsstring[]Top flagged behaviors, sorted by frequency (max 6).
reportCountnumberTotal number of reviews submitted for this user.
updatedAtstring | nullISO 8601 timestamp of the most recent review.

The reviewCounts object always has four keys:

KeyMeaning
comfortableReviewer felt comfortable interacting with this user.
neutralNo strong feeling either way.
uncomfortableReviewer felt uncomfortable.
veryUncomfortableReviewer felt very uncomfortable or unsafe.

Status Labels

The status field contains one of seven possible labels. These are computed from a recency-weighted scoring algorithm — recent reviews carry more weight than older ones.

LabelWhat it means
Not enough dataNo reviews yet, or only neutral reviews. Cannot determine safety.
Positive signalStrong positive sentiment with sufficient review volume (3+).
Mostly positivePositive-leaning signal, may have limited reviews.
Mixed signalA mix of positive and negative reviews. No clear direction.
Emerging concernRecent negative reviews are starting to appear.
Elevated concernSignificant negative signal with moderate confidence.
Strong concernConsistently negative signal with sufficient volume (3+).

The harshest labels (Positive signal, Strong concern) require at least 3 reviews. This prevents a single report from producing extreme labels.

Behavior Tags

When users submit reviews on SafeAdd, they can optionally flag specific behaviors. The behaviorTags array contains the most commonly flagged behaviors for this user, sorted by frequency. Up to 6 tags are returned.

Common behavior tags include:

TagDescription
asked personal infoRequested real name, age, location, or other personal details.
moved off platformTried to move the conversation to Discord, Instagram, etc.
persistent pressureContinued pushing after being told no or ignored.
inappropriate languageUsed sexual, violent, or otherwise inappropriate language.
scam attemptAttempted to scam through fake trades, links, or giveaways.
bullying or harassmentTargeted harassment, insults, or intimidation.
cheating or exploitingUsed cheats, exploits, or hacks in games.

An empty behaviorTags array means no specific behaviors have been flagged.

Flags

The flags array contains time-shape indicators about the user's review pattern. These help you understand the trajectory, not just the current state.

FlagWhen it appears
Recent concern pattern2 or more negative reviews (uncomfortable/very uncomfortable) in the last 30 days.
Recent positive pattern2 or more positive reviews (comfortable) in the last 30 days.
Limited contextThe user has only 1–2 total reviews. Take the status with a grain of salt.

Rate Limits

Your monthly request quota depends on your plan. Every API call — including lookups for unknown users — counts as one request.

PlanPriceMonthly Requests
Free$0100
Hobby$4.99/mo1,000
Builder$9.99/mo5,000
Pro$19.99/mo20,000

Usage resets on the 1st of each month (UTC). When you hit your limit, the API returns 429 Too Many Requests with a message telling you to upgrade.

You can check your current usage anytime from the dashboard.

Error Handling

The API uses standard HTTP status codes. Error responses always include a JSON body with an error field.

StatusMeaningExample
400Bad request — invalid username format.{ "error": "Invalid username..." }
401Unauthorized — missing, malformed, or revoked API key.{ "error": "Invalid API key." }
429Rate limited — monthly quota exceeded.{ "error": "Monthly request limit reached..." }
500Server error — something went wrong on our end.{ "error": "Internal server error." }

A successful lookup always returns 200, even if the user hasn't been reviewed yet. Check the reportCount field to determine if there's actual data.

Code Examples

Below are complete, copy-paste-ready examples for common use cases.

JavaScript / TypeScript

Works in Node.js, Deno, Bun, or any environment with fetch.

lookup.ts
const API_KEY = "sk_live_your_api_key";

async function lookupPlayer(username: string) {
  const res = await fetch(
    `https://api.safeadd.io/v1/profile/${username}`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );

  if (!res.ok) {
    const body = await res.json();
    throw new Error(body.error ?? `HTTP ${res.status}`);
  }

  return res.json();
}

// Usage
const data = await lookupPlayer("Builderman");
console.log(data.status);       // "Mostly positive"
console.log(data.reportCount);  // 17
console.log(data.reviewCounts); // { comfortable: 14, neutral: 3, ... }

Python

Uses the built-in requests library.

lookup.py
import requests

API_KEY = "sk_live_your_api_key"
BASE_URL = "https://api.safeadd.io/v1"

def lookup_player(username: str) -> dict:
    res = requests.get(
        f"{BASE_URL}/profile/{username}",
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    res.raise_for_status()
    return res.json()

# Usage
data = lookup_player("Builderman")
print(data["status"])        # "Mostly positive"
print(data["behaviorTags"])  # []

# Check if the user has concerning signals
if data["status"] in ("Elevated concern", "Strong concern"):
    print(f"Warning: {username} has safety concerns")

Roblox (Luau)

Use HttpService in a server-side Script to check player safety when they join your game. You must enable HTTP requests in Game Settings → Security.

Important: Never put your API key in a LocalScript or any client-side code. Players can see client-side code. Always use a server-side Script or keep the key in a private data store.
ServerScriptService/SafeAddCheck.lua
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")

local API_KEY = "sk_live_your_api_key" -- keep this server-side only

local function checkPlayer(player)
    local url = "https://api.safeadd.io/v1/profile/" .. player.Name

    local success, result = pcall(function()
        return HttpService:RequestAsync({
            Url = url,
            Method = "GET",
            Headers = {
                ["Authorization"] = "Bearer " .. API_KEY,
            },
        })
    end)

    if not success then
        warn("[SafeAdd] Request failed: " .. tostring(result))
        return nil
    end

    if result.StatusCode ~= 200 then
        warn("[SafeAdd] HTTP " .. result.StatusCode)
        return nil
    end

    local data = HttpService:JSONDecode(result.Body)
    return data
end

Players.PlayerAdded:Connect(function(player)
    local data = checkPlayer(player)
    if not data then return end

    print(player.Name .. " → " .. data.status)

    -- Example: warn moderators about concerning players
    if data.status == "Strong concern" or data.status == "Elevated concern" then
        -- Send to your admin notification system
        warn("[SafeAdd] ⚠ " .. player.Name .. " has status: " .. data.status)

        -- Optionally check specific behaviors
        for _, tag in ipairs(data.behaviorTags) do
            warn("  Flagged: " .. tag)
        end
    end

    -- Example: store the data for your moderation UI
    player:SetAttribute("SafeAddStatus", data.status)
    player:SetAttribute("SafeAddReports", data.reportCount)
end)

You can also use this to gate access to sensitive features. For example, restrict trading with flagged players or add extra verification steps:

Example: gate a feature based on status
local ALLOWED_STATUSES = {
    ["Positive signal"] = true,
    ["Mostly positive"] = true,
    ["Not enough data"] = true,
}

local function canTrade(player)
    local status = player:GetAttribute("SafeAddStatus")
    if not status then return true end -- no data yet, allow
    return ALLOWED_STATUSES[status] == true
end

Discord Bot (discord.js)

Add a /check command to your Discord bot that lets members look up a Roblox player's safety status.

commands/check.js
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");

const API_KEY = process.env.SAFEADD_API_KEY;

const STATUS_COLORS = {
  "Positive signal":  0x22c55e,
  "Mostly positive":  0x22c55e,
  "Not enough data":  0x9ca3af,
  "Mixed signal":     0xf59e0b,
  "Emerging concern": 0xf97316,
  "Elevated concern": 0xef4444,
  "Strong concern":   0xdc2626,
};

module.exports = {
  data: new SlashCommandBuilder()
    .setName("check")
    .setDescription("Check a Roblox player's safety status")
    .addStringOption((opt) =>
      opt.setName("username").setDescription("Roblox username").setRequired(true)
    ),

  async execute(interaction) {
    await interaction.deferReply();

    const username = interaction.options.getString("username");
    const res = await fetch(
      `https://api.safeadd.io/v1/profile/${username}`,
      { headers: { Authorization: `Bearer ${API_KEY}` } }
    );

    if (!res.ok) {
      return interaction.editReply("Failed to look up that player.");
    }

    const data = await res.json();
    const color = STATUS_COLORS[data.status] ?? 0x6b7280;

    const embed = new EmbedBuilder()
      .setTitle(data.username ?? username)
      .setColor(color)
      .addFields(
        { name: "Status", value: data.status, inline: true },
        { name: "Reports", value: String(data.reportCount), inline: true },
      );

    if (data.behaviorTags.length > 0) {
      embed.addFields({
        name: "Flagged Behaviors",
        value: data.behaviorTags.join(", "),
      });
    }

    if (data.flags.length > 0) {
      embed.setFooter({ text: data.flags.join(" · ") });
    }

    await interaction.editReply({ embeds: [embed] });
  },
};

Health Endpoint

GET/v1/health

Returns the API status. No authentication required. Use this to verify connectivity or for uptime monitoring.

Response
{
  "status": "ok",
  "version": "1.0.0"
}