Docs

Signal

What is known about an account, a server or a link, and what your server does about it.

What Signal is

Signal is the safety product. It answers one question in four ways: what do we know about this account, this server, this link, and what should happen here. The shared registry is the centre of it, and your protection level decides what your server does with what it says.

Scam detection, OCR and Join Guard are how Signal finds things out. They are instruments rather than the point: they read what is posted, read the text inside images, and check people at the door, and everything they catch becomes evidence in the same place. You do not tune them one by one; you pick a protection level and Signal decides what that means for each of them.

Whatever catches something, it ends up in the same record. A scam message, scam text read out of a screenshot, a report a member sends in, a ban list a server shares, a server somebody names, and a partner submission all become the same kind of evidence, counted once per independent source, and gathered onto one case for a reviewer. Five servers catching the same scammer within an hour is one piece of work with five pieces of evidence.

Warning

The three switches at the top of the Overview tab decide whether anything runs at all. A protection level on top of switched-off modules stores a preference and protects nobody.

Protection level

One choice, five options. Standard is the default.

Basic

Known bad only: the global scammer list and known scam links. The AI classifier is never consulted, so this level cannot produce a new false positive. Scam messages are deleted, nothing is banned. Detection threshold 95.

Standard

The default. Deletes scam messages and times the sender out for an hour, checks new members against the global scammer list, quarantines on a high-confidence hit, and reports the catch to Signal. Detection threshold 80.

Strict

Bans on a high-confidence detection, bans flagged scammers the moment they join, scans images for scam text, and re-checks existing members every 15 minutes. Detection threshold 70.

Lockdown

For a server under active attack. Everything Strict does, at the lowest threshold, plus brand-new accounts are held for verification before they can talk and low-confidence hits are punished too. Detection threshold 55.

Custom

Hand-tuned. Your stored settings are used exactly as they are and no preset applies. A server lands here by itself as soon as you change anything in the granular panel.

Info

A detection fires at confidence equal to or above the threshold, so a lower threshold is the stricter setting.

What a level does not touch

These stay yours whichever level you pick, and moving between levels keeps them:

Log channel, alert channel, quarantine role, exempt roles, exempt channels, exempt categories, server blocked domains, allowed domains, content allowlist and content exceptions.

Trust gate

The second half of the same decision: how careful to be with accounts this server does not know yet. A member's trust score is a number; the gate says what happens below a threshold.

ModeWhat it does
OffThe default. Scores stay visible to your moderators, nothing is blocked.
Limit linksBelow the threshold, links and server invites are removed. Every other message is left alone.
Require verificationBelow the threshold on join, the member goes through the Join Guard verification you already set up. Join Guard has to be on.
Hold at the doorBelow the threshold on join, the member gets the Join Guard quarantine role and waits for a moderator. No challenge is offered. Join Guard has to be on and needs a quarantine role.

The threshold is limited to 20 up to 70. Below 20 the gate catches nobody the shield has not already handled; above 70 it starts catching ordinary members. The default follows your protection level: 30 on Basic, 45 on Standard and Custom, 55 on Strict, 65 on Lockdown.

A gated member is always told their score, the threshold and how to contest it.

Info

If the dashboard cannot be reached the gate lets the member through. Signal is what stops scams; a gate is a convenience, and silently muting members over an internal hiccup is the worse failure.

Signal Radar

Every server that runs Signal reports its high-confidence catches. When the same indicator turns up in at least 3 unrelated servers inside 45 minutes, Radar opens a wave: that is a campaign, not one local spammer.

Servers in the wave are warned and temporarily raised one step up the ladder. The boost never overwrites the level you chose; it is a time-boxed flag that both the bot and the dashboard resolve when they read your settings, and it rolls back on its own.

On a Custom server a boost does not replace your config either. It tightens the threshold and makes sure joins and messages are checked, and it never raises a threshold you already set lower.

What happens on a detection

Which action fires depends on your level and on the confidence of the hit. Levels split it into a low-confidence and a high-confidence action, and there are levels where the low-confidence one is deliberately nothing at all.

  • The message is deleted.
  • The account is timed out, quarantined or banned, depending on the level.
  • A high-confidence catch is reported to Signal, where it can feed a Radar wave.
  • The action is written to your log channel, if you set one.
  • The member is told what happened and how to contest it.

What is recorded afterwards

A confirmed case becomes evidence in the Signal Registry, credited to the server that saw it rather than to Modora. Scores count one contribution per independent source, so ten catches in one server stay one source and three servers seeing the same account is what adds up.

Modora counts as one source however many places a ban lands. That is deliberate: a shared record in which the operator can clear everybody’s threshold on its own is the record that gets accused of running the ecosystem.

Signal Registry

The shared dossier on an account: what it is listed for, who agrees and when the listing runs out.

The Signal Registry is a shared dossier on Discord accounts. Signal already judges behaviour as it happens: it reads a message, scores it and acts. The registry answers the other question, the one a server owner has before anybody has posted anything, which is whether this account is trouble somewhere else.

One account, one row. It holds the labels the account carries, how strongly each one is carried, how many independent parties are behind it and the date it runs out. What that means inside your server is your decision, not ours.

Info

A listing is not a punishment. Nothing happens in your server until you switch the registry on there and say what each label should do.

Where the data comes from

Six kinds of evidence. Five of them are something a participant handed us; the sixth is what our own bot can already see in a server a reviewer confirmed is a problem.

KindWhat it is
Staff decisionA Modora reviewer read the case and decided. The only kind that can carry a label on its own, because it is the only kind where a person looked at the evidence.
DetectionSignal caught the account in a server that invited our bot.
ReportSomebody sent it in with proof, through a Modora surface.
Shared ban listA participating server chose to share its own ban list. It tells us that a server acted, not why, so it is worth the least and ages out fastest.
Community linkThe account was reported for acting on behalf of a community that is itself reported. That link comes from the report and from nowhere else.
Presence and standingThe account was seen in a server a reviewer confirmed. Being a member of one carries the associate label, which is capped at a log line. Running one, which we read from Discord permissions rather than from role names, is evidence for what that server was confirmed as. It is the weakest and shortest-lived kind here, it counts as one source however many members a server has, and it ends the moment somebody leaves.

Warning

Nothing in the registry is scraped. We never read the member list of a server, and a listing is never derived from a role somebody holds or a server somebody is in. The bot this replaces did exactly that, and there is no table here to put a member list in.

Labels

The vocabulary is generic on purpose. A registry that only understands one industry can only protect one industry.

LabelWhat it means
ScammerTook money, or tried to.
LeakerDistributed someone else's paid work.
ResellerSold on what was not theirs to sell.
CheaterRan or sold cheats.
RaiderOrganised disruption of a server.
AdvertiserMass DM and unsolicited promotion.
CompromisedAn account acting for somebody else after being taken over. Advisory only: it can never justify more than a log line.
AssociatePresent around one of the above without an act of their own. Advisory only, and never enforced.
OtherSomething real that none of the labels above describes.

How a label is carried

A label is a sum of evidence and nothing else. Each kind is worth a fixed amount out of a maximum of 100: a staff decision 80, a detection 35, a report 30, a shared ban list 25, a community link 20.

The sum counts one contribution per independent source, and only the strongest piece from that source. Ten reports from one server is one server disagreeing with the account; two reports from two servers is a pattern. That cap is what keeps a single angry party from driving a listing.

Withdraw the evidence and the label drops with it, in the same step. No listing outlives the reason for it.

Tip

A submitter with something to gain from the account being removed, a competitor or a party to the same dispute, counts for half. It is kept, because a competitor is often the first to notice a real scam, and it is marked, because it cannot carry a case on its own. A server can also refuse to act when every source behind a label is an interested party.

Servers, and the people running them

A server can be listed in its own right, and that is a separate judgement from anything about its members.

A reported server is checked by a person before it means anything. Once confirmed, it gets a rung: recorded and nothing more, a warning anyone asking about it will be told, or invites to it being removed in servers that asked for that. No rung removes a member of it. That runs through the account dossier, with the evidence and the appeal that belong to it.

We keep what a server has called itself, because renaming is the cheapest way around a listing, and every invite we have seen, because rotating codes is the second cheapest. Both come from what Discord tells anybody who hovers the link.

Where our bot is already inside a confirmed server, it records who is there and how far in. Being a member is associate: a log line, and no setting of ours can make it more than that. Holding the permissions to remove people is a claim about conduct, and it becomes evidence for whatever that server was confirmed as. It is still one source, so one bad server is never a case on its own.

Warning

Your server decides what association means inside your walls, through its own separate setting, and it defaults to logging. Anybody removed for it is told that it was about where their account has been seen and not about anything they posted, with the route to appeal in the Modora Discord server.

What it will not do

These are limits in the code, not promises in a policy document.

  • It only reads a member list in a server a reviewer confirmed and our own bot is already in. Never from a user account, never from a server that did not invite us, and never from a list somebody else collected.
  • It never enforces association on its own. Presence carries associate, which is a log line everywhere by default and can only be raised by your own server, deliberately, for people who actually run such a place. Modora itself never turns it into a global ban or a network listing.
  • It never acts on one interested party. Their evidence counts for half, and a label carried only by such sources cannot be enforced.
  • It never hands out the case file. Who reported an account, and what they wrote, stays inside Modora.
  • It never bans on our say-so. Your server picks the action for every label and does nothing at all until you switch the registry on.
  • It never keeps a standing after somebody leaves. Departure ends it the same day, and the evidence goes with it.

Everything expires

Evidence has a lifetime: a staff decision 730 days, a detection or a report 365, a community link 270, a shared ban list 180. When the last piece behind a label runs out, the label stops counting.

An account nobody reports again is swept hourly, so a listing actually lapses instead of sitting in the table forever. Only a person can take a single listing out of the clock, and that is a deliberate staff decision on that one account.

What your server does with it

The dossier is shared. The consequences are not.

The registry stays off in a server until you switch it on. Each label gets its own action: nothing, a log line, a role, a kick or a ban. Labels you have not configured fall back to a log line.

Two thresholds guard every action: the confidence a label needs, and the number of independent sources behind it. They start at 70 and 2, so nothing is taken away from anybody on one party's word. A label that misses either bar still produces a log line, with the reason it was held back, because that is the message that makes the thresholds worth having.

Info

A server starts in dry run: the registry reports what it would have done and does nothing. Checking members who are already inside is opt-in and stays off until you ask for it.

Appealing

Every enforcement carries the route to contest it, and the route stays open. A network-level action (global ban, blacklist, network quarantine) can only be lifted by Modora, so those always start at modora.gg/signal/appeal, which shows what is on the account and hands over to a ticket in the Modora server.

A Modora appeal is a conversation, not a form. A listing turns on evidence somebody else supplied, and the questions a reviewer has to ask about it - which server, what did you post, do you still have the message - do not fit in one box that gets read once. So it is a ticket: a person answers, the member can answer back, and both sides keep the record. Anyone too restricted to join a server can mail support@modora.gg instead.

A ban that a single server issued goes to that server's own appeal form when one is configured; otherwise it falls back to the Signal page. Members can also use /appeal in the server itself if the Appeals module is on there.

Your own listing is part of the trust check for your account at /signal/check: whether you are listed, what for, how many independent sources are behind it and the month it runs out. Dates are month precision only, because a timestamp to the day would tell the server that reported exactly which of its reports this was.

An upheld appeal clears the account. Cleared blocks enforcement outright and is sticky: it survives new evidence, so the next report cannot quietly undo an appeal somebody won. Reopening a cleared account is a separate staff decision. A rejected appeal changes nothing in the registry.

Tip

If Signal caught something it should not have, report it as a false positive rather than only appealing: that is what stops it from happening again elsewhere.

The API

What another system can ask Signal, and with which key.

Every endpoint below is keyed and scoped. A key is issued per partner and names exactly what it may reach, so a key for reading a record cannot file one. Keys and their scopes are managed on dev.modora.gg.

EndpointPartScopeWhat it does
GET /api/signal/v1/meYour keyno scope neededDetails of the key you are using.
GET /api/signal/v1/statusYour keyno scope neededConfirm the key is accepted and see which version answers. No scope required.
GET /api/signal/v1/usageYour keyno scope neededWhat this key has used, against its per-minute and per-day limits.
GET /api/signal/v1/alertsSignal Radarsignal.alerts.readAlerts raised by the network, plus those for your own server. Each carries a scope of network or guild.
GET /api/signal/v1/campaignsSignal Radarsignal.campaigns.readActive scam campaigns.
POST /api/signal/v1/canonicalizeSignal Radarsignal.detectNormalise a URL to its canonical form.
POST /api/signal/v1/detectSignal Radarsignal.detectRun text through the scam classifier.
GET /api/signal/v1/domain-checkSignal Radarsignal.domain.lookupCheck one domain against the known-scam list. flagged means Signal treats it as a scam domain, the same test detection itself applies; listed means the domain is on file but no verdict has been reached.
POST /api/signal/v1/domainsSignal Radarsignal.domain.createPropose a domain for the known-scam list. It is recorded as PENDING and stays off the block list until Modora staff confirm it, so domain-check keeps answering flagged: false in the meantime.
GET /api/signal/v1/eventsSignal Radarsignal.stats.readThe network detection feed: when, how severe, which domain. Never a server, channel or message.
GET /api/signal/v1/feedsSignal Radarsignal.domain.lookupWhere the domain list comes from and how recently each source added anything.
POST /api/signal/v1/ocrSignal Radarsignal.ocr.submitSend an image through the scam-text scanner. Requires image_url; extracted_text is optional and is weighed together with whatever the scanner reads. The answer carries scanned, which is false when the scanner was unavailable and the submission was stored on the supplied text alone.
GET /api/signal/v1/statsSignal Radarsignal.stats.readDetection counts across the network.
GET /api/signal/v1/trendsSignal Radarsignal.stats.readDetection trends over time.
POST /api/signal/v1/banrequestSignal Registrysignal.report.createOpen a ban request case. Requires user_id and reason; optional proof_url, notes, guild_id, guild_name, submitted_by, submitted_by_username. Every id is a string, never a JSON number. Asking again for an account that is already waiting returns the existing case with duplicate: true instead of opening a second one.
GET /api/signal/v1/banrequest/{caseId}Signal Registrysignal.report.createRead the state of a ban request case. The case id is the MODORA-BR-... value returned when the case was opened.
GET /api/signal/v1/lookup/{userId}Signal Registrysignal.trust_checkWhether an account is on the known-scammer list, and the month it was listed. Not the note behind it.
POST /api/signal/v1/passportSignal Registrysignal.passport.readThe full portable verdict on an account: score, categories and months. Never the message, channel or server behind it.
GET /api/signal/v1/registry/{userId}Signal Registrysignal.trust_checkThe registry verdict on an account: whether it is listed, its labels with confidence and how many independent sources agree, plus the month it was listed and the month it expires. Never the evidence, the submitters or the servers behind it.
POST /api/signal/v1/reportsSignal Registrysignal.report.createFeed a scam report into the network. It goes through the same intake as a report from Discord, so if a report about that account is already open your evidence is added to it instead of a second one being opened, and the answer carries that report's ticket_number. Requires message_text. Screenshots go in image_urls, up to ten of them; image_url still takes a single one and becomes the first of the list. A submission that names no reported_user_id is accepted like any other and opens a report of its own. Duplicates: the same account again from the same key within 24 hours answers 422 duplicate, and where no account is named, the same submission text again from the same key within that window does.
GET /api/signal/v1/reports/{ticket}Signal Registrysignal.report.createRead back a report you filed: status, decision, decided_at and the risk score. Only reports filed with this key; anything else answers 404, whether it does not exist or belongs to someone else.
POST /api/signal/v1/trust-checkSignal Registrysignal.trust_checkAsk for the trust verdict on a Discord account: a score and whether it is flagged.

Info

These parts have no public endpoints yet: Signal Shield, Join Guard.

Send your Signal API key as X-API-Key. The endpoint needs the signal.trust_check scope, the same one /lookup uses, and sits on the same rate limit as the rest of the v1 surface.

Request
GET /api/signal/v1/registry/{user_id}
X-API-Key: SIGNAL-...
Response
{
  "user_id": "123456789012345678",
  "listed": true,
  "primary_label": "scammer",
  "confidence": 65,
  "source_count": 2,
  "labels": [
    {
      "label": "scammer",
      "confidence": 65,
      "source_count": 2,
      "expires_month": "2027-03"
    }
  ],
  "listed_month": "2026-03",
  "expires_month": "2027-03",
  "standing": "admin"
}

listed is the verdict. confidence and source_count are the two numbers your thresholds are set against. listed_month and expires_month are month precision; expires_month is null when the listing does not run out on its own, which only happens when a reviewer decided that.

Warning

An external key never receives evidence rows, the servers that reported, the names of submitters or anything else that identifies a source. Those exist in the dossier and stay inside Modora, exactly as with /lookup.

Commands

/signal status

Shows what Signal is protecting this server with right now.

/signal link

Checks whether a link or domain is known to Signal.

/signal user

Checks whether an account has a Signal record. Moderators only.

/trust

Your own trust score, what it is built from, and how to appeal it.

/trustcheck

The trust and fraud status of another member. Staff or Manage Server.

/appeal

Submit an appeal for a moderation action in this server. Needs the Appeals module.

/report

Reports an account to the Modora Team. Use /report-server for a whole server.

Was this page helpful?