minsec

The blocklist feed

Two tiers, two address families, plain text, delta-capable. What minsec-sync pulls, and what you can pull yourself.

minsec-sync pull handles this for you. This page is for people who want to consume the feed directly — into a router, a CDN rule, another firewall — or who just want to know what their server is fetching.

Endpoint

GET https://api.minsec.io/v1/feed/{tier}/{family}

tier is basic or high; family is v4 or v6. The high tier is a higher-confidence subset of basic: more reporters, and agreement across more than one detection rule. Agents load v4 into crowd4 and v6 into crowd6.

The feed is unauthenticated for reading.

Format

Plain text. The first line is a header; every following line is one entry. A bare address means a single host, and prefixes are explicit:

# minsec-feed v1 full tier=basic family=4 snapshot=1234 generated=2026-08-23T12:00:00Z count=4821
203.0.113.7
198.51.100.0/24

Lines are sorted, and identical content always produces identical bytes — so the ETag is stable and a no-op refresh really is free.

Fetching politely

Poll hourly. That is what the packaged minsec-sync timer does, and it is the cadence the free feed is sized for — the list moves on the scale of hours, so a faster poll gains you almost nothing. Five minutes is a hard ceiling, not a target; Cache-Control: public, max-age=300 applies. In order of preference:

1. Conditional request. Send back the ETag you last saw:

curl -sS -H 'If-None-Match: "abc123"' \
  https://api.minsec.io/v1/feed/basic/v4

Unchanged content answers 304 with no body.

2. Delta. Send the snapshot id from your last pull:

curl -sS 'https://api.minsec.io/v1/feed/basic/v4?since=1200'
# minsec-feed v1 delta tier=basic family=4 from=1200 to=1234 count=37
+203.0.113.7
-192.0.2.55

Apply + as additions and - as removals. Snapshots are retained about 14 days; older cursors, or a delta that would be larger than the full list, get a full response instead — in which case replace the whole set atomically. Either way, store the new snapshot id and ETag.

Expiry

Entries carry no TTL in the feed itself. Expiry is server-side and reaches you as - lines, or as absence from the next full listing.

Give your set a failsafe timeout anyway, and make it much longer than your poll interval — a day against an hourly poll is a reasonable ratio. Without one, a consumer whose polling stops keeps enforcing its last snapshot forever, and a stale crowd list blocks addresses we have delisted or that have since been reassigned to somebody innocent. With one, the list decays instead.

The catch is that a timeout only helps if a working consumer keeps resetting it, and deltas do not: they touch what changed and leave everything else ageing. So refetch the full listing unconditionally on a timer at a fraction of the timeout — ignoring your ETag, or the server will answer 304 while your set quietly drains — and replace the set with it. minsec-sync uses a 24-hour timeout with a forced full replace every 12 hours.

Errors

503 feed_not_ready with a Retry-After can occur briefly after a fresh backend deployment. Treat it as "try again shortly", not as an empty list.

A minimal consumer

#!/bin/sh
# Refresh an nftables set from the basic v4 feed. Deliberately simple:
# no delta handling, so run it hourly from cron.
set -eu
curl -fsS https://api.minsec.io/v1/feed/basic/v4 \
  | grep -v '^#' \
  | paste -sd, - \
  | xargs -I{} nft "flush set inet myfw crowd4; add element inet myfw crowd4 { {} }"

For anything more serious, use minsec-sync — it does conditional fetching, deltas and atomic replacement, and it is already installed.

Wire details: API reference.