Every spot carries two independent verdicts. Moderation decides whether it’s on the public map at all. Community status says how much the public trusts it.
Spots
A spot is an approximate place (not an address, and never a person) where people report being attacked or killed. It has:
- A category:
murder,mob_action,boda_gang,robbery,stabbing,kidnappingorother. - A severity from 1 to 5.
- A time of day when it’s dangerous:
day,nightorany. - An optional source, such as a news report, police statement or court record.
| Severity | Meaning |
|---|---|
| 1 | Feels unsafe |
| 2 | Harassment or threats |
| 3 | Robbery or snatching |
| 4 | Violent attack |
| 5 | Someone was killed |
Spots with seeded: true come from the project’s sourced seed data
(data/seed.json) rather
than from reports made in the app.
Moderation
Moderation isn’t exposed as a field in the public API. You only ever see approved spots, but
it controls what GET /api/spots returns:
| Moderation state | On the public map? | How it gets there |
|---|---|---|
approved |
Yes | New reports, when the site publishes instantly (the default). A moderator approves or restores it. |
pending |
No | New reports, when the site holds reports for review. A spot auto-hidden by flags. |
rejected |
No | A moderator hides or rejects it. It can be restored later. |
POST /api/spots tells you which case applies: pending: true means the report is waiting for
a moderator. Check requireApproval from GET /api/me to warn users up front.
Moderators can also verify a spot after checking it against a reliable source. Verified
spots have moderator_verified: true. Show them as more trustworthy, for example with a
“Verified by moderators” badge. They’re never auto-hidden by flags.
Community status
Votes answer one question: is this place still dangerous? After every vote, the status is recalculated from the totals:
| Condition | status |
|---|---|
confirmations − denials ≥ 3 |
confirmed |
denials > confirmations + 2 |
disputed |
| otherwise | unverified |
- A new report starts with
confirmations: 1, the reporter’s own vote. last_confirmed_atupdates on every “still dangerous” vote. Use it to show freshness, e.g. “confirmed 2 days ago”.- Disputed spots stay on the map but are ignored by the route check. Consider dimming them in your UI.
Flags
Anyone can flag an approved spot with a reason (inaccurate, names_person, duplicate,
abusive, resolved or other) and an optional note. Flags land in the moderators’ review
queue.
When a spot reaches the auto-hide threshold of open flags (3 by default, set by admins),
it moves to pending and disappears from the map until a moderator decides:
- Keep it: the flags are dismissed and it returns to the map.
- Hide it: it becomes
rejected. - Edit it: fix the location or details, then keep it.
POST /api/spots/{id}/flag returns result: "hidden" when your flag triggered the auto-hide.
Search
GET /api/search looks in two places at once:
- Mapped spots, straight from the database, with fuzzy matching (Postgres
pg_trgm), so typos like “kalerw” still find Kalerwe. - Places from OpenStreetMap, cached in the database for 30 days. Each place lists the mapped spots within 2 km, so searching a place that isn’t mapped (e.g. “Mulago”) still warns about danger nearby (Kalerwe, 1.2 km).
Identity
The API has no accounts. Each visitor is identified by
sha256(VOTER_SALT | client IP | user agent), truncated. This hash:
- enforces one vote and one flag per spot per visitor,
- keys the per-visitor rate limits,
- is never returned by the API or readable through the database’s public role.
Report limits also apply per network (IP only), so a server-side integration that proxies many users through one IP should expect them to share that limit. See Rate limits.