← Back to docs

DNSBL / FraudBL API Reference

Language: EN | EN | SV

Endpoint: dnsbl - current ToolsAPI DNSBL / FraudBL model

Status

This page now documents the current ToolsAPI model used on tools.tornevall.* together with the current WordPress DNSBL plugin behaviour.

The historical DNSBL v5 / API v3 interface is deprecated, phased out, and no longer the active integration model. A legacy reference is kept at the bottom of this page for compatibility and migration work.

GDPR notice

Older blacklist workflows sometimes stored more contextual material around why a host or message was flagged. That is no longer the design target.

In the current ToolsAPI direction, GDPR and data minimization must be considered before data is published to DNS. Because the live reputation layer is now distributed through DNS zones and zone files rather than a real-time database API, anonymization and minimization must happen in the publication flow itself. Only data required for classification, abuse mitigation, and fraud protection should be exposed.


Table of contents


Current status

With "our API" in this document, we mean ToolsAPI, hosted on tools.tornevall.*.

Today, the active model is:

  • reputation data is ultimately consumed through DNS lookups
  • ToolsAPI exposes the DNS zone and DNS record APIs used to inspect and maintain DNS-backed data
  • the WordPress plugin performs direct DNS lookups and keeps its own local cache/statistics
  • old 3.0/dnsbl request semantics are no longer the primary public contract

Important consequence

There is not a new public one-to-one replacement for the old PUT /3.0/dnsbl / DELETE /3.0/dnsbl workflow documented here as an active API.

Instead, the current stack is built around:

  1. DNS publication and zone management in ToolsAPI
  2. direct DNS resolution by consumers such as the WordPress plugin
  3. internal DNSBL/DNS handling for specialized blacklist flows, including commerce-related publication

Quick start

If you are integrating with the current DNSBL/FraudBL stack, the normal order is:

  1. validate the token with GET /api/dnsbl/token/info
  2. inspect the current live state with POST /api/dnsbl/check-ip
  3. add, update, delete, or bulk-submit through /api/dnsbl/records/*
  4. use the helper endpoints for engine/runtime flows when needed:
    • GET /api/dnsbl/stats
    • GET /api/dnsbl/engine-settings
    • POST /api/dnsbl/forum/bounce
    • POST /api/dnsbl/dmarc/report

DNSBL stats and counters

Tools now exposes GET /api/dnsbl/stats for API-readable DNSBL counters.

  • Auth matches the normal DNSBL helper routes: active DNSBL tokens via X-Dnsbl-Token / dnsbl_token, active DNSBL provider keys, and admin/session contexts that already resolve for /api/dnsbl/*.
  • stats.api_queries summarizes recorded /api/dnsbl/* traffic from the local api_endpoint_visits tracker.
  • stats.mutations summarizes database-backed logical DNSBL add/delete/update outcomes, including successful writes, dry-runs, failures, and delete no-op cases such as already_not_listed.
  • Important scope note: older operator logs, Slack audit messages, and mail audit trails existed before this endpoint, but they were not a stable API-readable counter source for DNSBL add/delete POST requests. The new stats endpoint is therefore the canonical counter surface going forward.

Example:

curl -s "https://tools.tornevall.net/api/dnsbl/stats" \
  -H "X-Dnsbl-Token: YOUR_TOKEN"

Public DNSBL statistics page and whitelist visibility

Tools also now has a public browser page at:

/dnsbl/statistics

That page combines:

  • the same DNSBL API counter data that powers GET /api/dnsbl/stats, and
  • the active whitelist rows loaded from DNSBL_V5.ipwhitelist.

Important whitelist behavior notes:

  • the whitelist is not only a passive testing list,
  • it is also consulted while spam-handling / blacklist-processing runs so exempted IPs can be removed instead of being left behind in the DNSBL zones,
  • description is now shown publicly when it is not empty,
  • is_local_network marks rows that represent local-network exceptions that are normally supposed to stay out of blacklist publication and therefore also be purged from the DNSBL zones when they appear there.

Admin note:

  • /admin/dnsbl/engine-settings now also shows the same whitelist/local-network overview,
  • and it now includes a dedicated Purge local networks now action that removes currently listed blacklist owners whose decoded IP matches the active local-network IP/CIDR rows from ipwhitelist.

DNSBL auth transport

For DNSBL-oriented endpoints, the delegated auth model is still a DNSBL token sent as either:

  • X-Dnsbl-Token: <token>
  • dnsbl_token=<token>

Depending on the endpoint, active DNSBL provider keys and active admin-owned Tools tokens can also resolve through the same transport.

Minimal curl examples

Check token status:

curl -s "https://tools.tornevall.net/api/dnsbl/token/info?dnsbl_token=YOUR_TOKEN"

Check one IP:

curl -s "https://tools.tornevall.net/api/dnsbl/check-ip" \
  -H "Content-Type: application/json" \
  -H "X-Dnsbl-Token: YOUR_TOKEN" \
  -d '{"ip":"203.0.113.4"}'

Delete one IP (server-side delist scope resolution):

curl -s "https://tools.tornevall.net/api/dnsbl/records/delete" \
  -H "Content-Type: application/json" \
  -H "X-Dnsbl-Token: YOUR_TOKEN" \
  -d '{"ip":"203.0.113.4"}'

Bitmask model used by ToolsAPI and the plugin

The returned reputation value is a bitmask integer.

Each bit represents a specific reputation state. Multiple bits can be active at the same time.

Bitmask values

Value Constant Meaning Status
1 FREE_SLOT_1_PREVIOUSLY_REPORTED Previously used for older reported-IP flows. Considered unreliable. Deprecated
2 IP_CONFIRMED IP address confirmed as a working proxy. Active
4 IP_PHISHING Host confirmed as phishing or fraud infrastructure. Active
8 IP_FRAUDCOMMERCE Reserved for e-commerce fraud classification. This value is occupied and must not be reused for anything else. Active
16 IP_MAILSERVER_SPAM E-mail spam source. Active
32 IP_SECOND_EXIT Secondary exit point, for example TOR exit or alternate abuse IP. Active
64 IP_ABUSE_NO_SMTP General abuse through web forms, attacks, telnet, forums, or similar channels. Active
128 IP_ANONYMOUS Anonymous proxy or anonymizing service. Active

How the bitmask works

You must treat the response as a sum of active bits, never as a single-status enum.

Example:

4 + 16 + 64 = 84

This means the host is simultaneously classified as:

  • phishing / fraud infrastructure
  • mail spam source
  • general abuse

Implementation rules

  • Multiple bits may be set at once.
  • Deprecated bits should be ignored in new logic.
  • Bit value 8 must use IP_FRAUDCOMMERCE.
  • Bit value 8 must not be recycled for an unrelated meaning.
  • Consumers should check each bit individually.

Compatibility note

Historical API v3 code used the name FREE_SLOT_8_PREVIOUSLY_PROXYTIMEOUT for bit 8. That name belongs to the legacy model only and should be considered obsolete in new documentation and new integration logic.


DNS zones and publication model

The current DNSBL / FraudBL stack is DNS-first.

Main zones

  • dnsbl.tornevall.org
  • bl.fraudbl.org
  • ecom.fraudbl.org

Normal behaviour

  • Standard DNSBL publication continues to use the ordinary DNS-backed model.
  • Fraud-related publication now mirrors into the ordinary DNSBL registry and bl.fraudbl.org.
  • In practice that means a fraud add can now create records in:
    • dnsbl.tornevall.org
    • opm.tornevall.org
    • bl.fraudbl.org
  • Commerce-related fraud publication must be able to exist in both:
    • the ordinary bl.fraudbl.org zone
    • the dedicated ecom.fraudbl.org zone

Commerce publication

When a commerce blacklist event is published, it should not only create a regular FraudBL-style entry. It should also create a dedicated commerce entry.

Example zone target:

X.X.X.X.ecom.fraudbl.org

The exact DNS name construction for commerce publication should be handled by the DNSBL/DNS layer, not manually reconstructed by external callers.

GDPR and anonymization in DNS

Because the current system publishes through DNS zone files, you cannot treat the data model like a database-driven API that can always be rewritten or anonymized afterward.

Therefore:

  • data minimization must happen before publication
  • only necessary classification data should be exposed
  • e-commerce publication should be designed with anonymization in mind
  • if encoding or hashing is used for commerce publication, the DNSBL component should own that encoding so it is applied consistently

Current ToolsAPI endpoints in active use

The active write/read surfaces in the current codebase are now exposed under both:

/api/dns
/api/dnsbl

The DNS endpoints remain the low-level DNS-backed write surface.

The DNSBL endpoints are convenience aliases for reputation publication where the caller sends an IP address plus a bitmask and lets the DNSBL layer decide owner encoding, zone selection, and commerce mirroring.

For delete / delist requests, the current controller is now authoritative on the server side:

  • the caller submits the IP address,
  • Tools performs the live DNS inspection itself,
  • the endpoint derives the concrete listed owner names in dnsbl.tornevall.org, opm.tornevall.org, bl.fraudbl.org, and/or ecom.fraudbl.org,
  • and the final low-level DNS DELETE operations are built from that live result.

Clients therefore no longer need to decide which blacklist subzone should be deleted for a given IP. Caller-provided publication_type / bitmask on delete is treated as a hint at most; the authoritative delist scope now comes from the server-side DNS lookup.

Delete no-op note:

  • when a delete request targets an IP that is already not listed anywhere in the live DNSBL/FraudBL zones, Tools now treats that as an accepted idempotent no-op instead of a hard invalid_dnsbl_publication error,
  • success payloads for that case now include additive reason="already_not_listed", already_not_listed=true, forced_success=true, and operation_count=0,
  • intended client behavior: treat that response as handled cleanup / already-delisted state, not as a retryable publication error.

Private-network hygiene note:

  • RFC1918 IPv4 ranges are never allowed in the live DNSBL / FraudBL zones.
  • Add/update publication attempts for 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 are now rejected before DNS writes are applied.
  • DNSBL add/update requests that hit this guard now return 422 with reason="private_ipv4_not_allowed_in_dnsbl".
  • Tools admin now also exposes a hygiene purge on /admin/dnsbl/engine-settings so operators can remove any historical private reverse-owner rows that may have leaked into dnsbl.tornevall.org, opm.tornevall.org, bl.fraudbl.org, or ecom.fraudbl.org before this guard existed.

Local-network whitelist hygiene note:

  • some blacklist exemptions are driven by active rows in DNSBL_V5.ipwhitelist, not only by the three hard-coded RFC1918 ranges,
  • rows flagged with is_local_network=1 are treated as table-driven local-network exceptions,
  • Tools now exposes those rows publicly on /dnsbl/statistics,
  • and the admin hygiene page can now purge currently listed blacklist owners whose decoded IP falls inside those active local-network IP/CIDR rows.

Operational note for DNS transport:

  • owner names still remain the concrete live-listed hosts such as 1.2.3.4.bl.fraudbl.org or 1.2.3.4.opm.tornevall.org,
  • but when those child blacklist zones are updated through dynamic DNS, Tools now sends the DNS UPDATE against the actual authoritative parent zone (fraudbl.org / tornevall.org) instead of incorrectly using the child zone itself as the update-zone section.

Operational guidance for helper/native clients:

  • when deleting one IP, send one delete request for that IP and let Tools expand the live-listed owners/zones itself,
  • do not fan out check-ip / delete_candidates into multiple follow-up delete requests for the same IP unless you are intentionally deleting multiple distinct IP addresses,
  • bulk delete should primarily be used for multiple IPs, not for multiple candidate families belonging to one already-submitted IP.

Authentication

The current DNS endpoints support these access modes:

  • authenticated web session
  • Authorization: Bearer <token>
  • X-API-Key: <token>
  • ?api_key=<token> query parameter
  • IP whitelist access, when enabled server-side

For /api/dnsbl/* writes specifically, a dedicated DNSBL token remains the normal delegated model. In addition, an active admin-owned Tools API key/token now receives automatic effective DNSBL access through the same X-Dnsbl-Token / dnsbl_token transport.

When a pending DNSBL token request is approved from /admin/dnsbl/tokens, Tools now also sends a requester-facing approval email confirming that the token is ready, listing the granted scope, and summarizing the current delete guardrails/default limits applied to that account.

DMARC intake and review queue

Tools now also exposes a DMARC intake path for dnsbl-engine / site-owner workflows:

  • POST /api/dnsbl/dmarc/report

Purpose:

  • accept DMARC XML / gzip / ZIP / MIME-wrapped DMARC payloads,
  • including forwarded/wrapped message/rfc822 mails where the actual DMARC XML/ZIP arrives as a nested attachment,
  • normalize them into stored report metadata plus per-source-IP rows,
  • keep the final publish decision in the site owner's hands instead of auto-blacklisting directly from the incoming report.

Auth rules:

  • DMARC intake now requires an active DNSBL token with add/write permission via X-Dnsbl-Token or dnsbl_token,
  • the token must resolve to an active DNSBL token whose effective can_add is true,
  • active DNSBL provider keys (provider=tornevall_dnsbl) are also accepted through the same token transport,
  • active admin-owned Tools API keys are also accepted through the same token transport as admin passthrough,
  • missing, invalid, or inactive DNSBL tokens now receive 401,
  • active DNSBL tokens without add permission now receive 403 with reason="insufficient_dnsbl_scope",
  • ordinary non-DNSBL Tools API keys, admin-session fallbacks, and anonymous/internal DMARC uploads are not accepted for this endpoint anymore.

Example request body:

{
  "payload_base64": "H4sIAAAAA...",
  "source_type": "dnsbl_engine_dmarc",
  "source_name": "dnsbl-engine",
  "original_filename": "mailru-report.xml.gz",
  "content_type": "application/gzip",
  "content_encoding": "gzip"
}

Typical success response:

{
  "ok": true,
  "duplicate": false,
  "message": "DMARC payload ingested for admin review.",
  "report": {
    "id": 12,
    "report_id": "33491921110551199191685577600",
    "org_name": "Mail.Ru",
    "policy_domain": "tornevall.net",
    "status": "pending",
    "record_count": 1,
    "source_ip_count": 1,
    "date_begin": "2023-06-01T00:00:00+00:00",
    "date_end": "2023-06-02T00:00:00+00:00"
  },
  "records": [
    {
      "id": 99,
      "source_ip": "60.13.8.218",
      "message_count": 1,
      "disposition": "reject",
      "dkim_result": "fail",
      "spf_result": "fail",
      "recommended_action": "spam_fraud",
      "status": "pending"
    }
  ]
}

Operational/admin flow:

  • ingested reports are reviewed in /admin/dnsbl/dmarc,
  • the site owner can publish one reported source IP as either spam (16) or spam + fraud (84),
  • rows can also be marked ignored without publication,
  • this queue is intentionally not a normal end-user feature.

DNSBL engine runtime settings

Tools now also exposes the normalized runtime settings used by dnsbl-engine:

  • GET /api/dnsbl/engine-settings

Purpose:

  • let helper clients confirm current server-side behavior without scraping admin HTML,
  • expose operator-configured flags such as forum-bounce handling and exempt forum groups,
  • keep standalone runners aligned with the current Tools admin configuration.

Auth rules:

  • same DNSBL auth transport as the other /api/dnsbl/* endpoints,
  • active DNSBL tokens, active DNSBL provider keys, admin passthrough, and existing admin/session-compatible DNSBL auth all work through the normal DNSBL resolver.

Typical success response:

{
  "ok": true,
  "message": "DNSBL engine settings loaded.",
  "settings": {
    "skip_delivery_status_notifications": true,
    "forum_bounce_enabled": true,
    "forum_bounce_group_id": 156,
    "forum_bounce_exempt_group_ids": [6, 7, 37]
  },
  "auth": {
    "auth_mode": "dnsbl_token",
    "scope_label": "add"
  }
}

Forum bounced-recipient intake

Tools now also exposes a dedicated intake path for forum-related bounce / mailer-daemon analysis from dnsbl-engine:

  • POST /api/dnsbl/forum/bounce

Purpose:

  • accept rejected forum-recipient addresses extracted from bounce / DSN mail,
  • match those rejected addresses against vBulletin forum users,
  • add a configurable bounced-email group (default 156) as a secondary forum group,
  • leave exempt groups (for example admin/operator groups) untouched.

Auth rules:

  • active DNSBL tokens are accepted through X-Dnsbl-Token / dnsbl_token,
  • active DNSBL provider keys (provider=tornevall_dnsbl) are also accepted,
  • active admin-owned Tools API keys are accepted through the same token transport as admin passthrough,
  • inactive tokens return 401, and ordinary non-DNSBL Tools tokens return 403 with reason="wrong_token_type".

Example request body:

{
  "rejected_recipients": [
    {"email": "user@example.net"},
    {"email": "second@example.net"}
  ],
  "source_type": "dnsbl_engine_forum_bounce",
  "source_name": "dnsbl-engine",
  "original_filename": "1700000000.12345.mail",
  "subject": "Delivery Status Notification (Failure)",
  "sender_identity": "MAILER-DAEMON <mailer-daemon@example.net>",
  "remote_host": "mx.example.net",
  "diagnostic": "550 5.1.1 User unknown"
}

Typical success response:

{
  "ok": true,
  "message": "Forum bounce recipients were processed against vBulletin users.",
  "summary": {
    "submitted_emails": 2,
    "matched_users": 1,
    "updated_users": 1,
    "skipped_users": 0,
    "already_grouped_users": 0,
    "exempt_users": 0,
    "unmatched_emails": 1,
    "dry_run": false
  },
  "updated_users": [
    {
      "userid": 123,
      "username": "ForumUser",
      "email": "user@example.net",
      "action": "update_membergroups",
      "new_membergroupids": "20,156"
    }
  ],
  "unmatched_emails": [
    "second@example.net"
  ],
  "settings": {
    "enabled": true,
    "target_group_id": 156,
    "exempt_group_ids": [6, 7, 37]
  }
}

Admin/operator note:

  • the current group ID and exempt group IDs are configured from the Tools vBulletin admin page,
  • Tools adds the bounced-email group as a secondary group only; it does not rewrite the user's primary forum group.

Zone discovery

GET /api/dns/zones

Lists zones available to the caller.

Possible access types in responses include:

  • authenticated
  • api_key_global
  • ip_whitelist

Example:

{
  "ok": true,
  "count": 2,
  "zones": [
    {
      "zone": "dnsbl.tornevall.org",
      "file": "...",
      "key": "dnsbl.tornevall.org"
    }
  ],
  "access_type": "authenticated"
}

Zone data

GET /api/dns/zones/{zone}

Returns zone data for a specific zone.

Supported read modes in the current controller are:

  • AXFR-based retrieval
  • file-based retrieval
  • cache-assisted UI flow via the dedicated cache endpoints below

The response contains paged zoneData records with fields such as:

  • name
  • type
  • rdata
  • ttl
  • class

GET /api/dns/zones/{zone}/axfr

Forces an AXFR-style fetch path in the current controller flow. AXFR is used because dynamic DNS updates may exist in journal state before they are flushed to local zone files.

GET /api/dns/zones/{zone}/cache

Checks whether a fresh cache entry exists.

Response source values currently include:

  • from_database
  • needs_axfr

If cache is available, the response can include:

  • cached_at
  • cache_age
  • cache_ttl_seconds
  • paged zoneData

GET /api/dns/zones/{zone}/search?q=...

Searches cached zone data only.

Important behaviour in the current implementation:

  • it never triggers AXFR on its own
  • it can use fresh cache or stale cache if no fresh cache exists
  • it supports:
    • exact IP search
    • CIDR search
    • plain text fallback search
  • DNSBL-style reversed owners are decoded back to forward IP form for matching

Possible response source values include:

  • from_database
  • from_database_stale

POST /api/dns/zones/{zone}/cache/clear

Clears the stored cache entry for one zone.

DNS record updates

These endpoints are the active write surface in the public ToolsAPI codebase:

  • POST /api/dns/records/add
  • POST /api/dns/records/delete
  • POST /api/dns/records/update
  • POST /api/dns/records/bulk

The same DNSBL publication logic is also reachable through:

  • POST /api/dnsbl/records/add
  • POST /api/dnsbl/records/delete
  • POST /api/dnsbl/records/update
  • POST /api/dnsbl/check-ip

Current implementation notes:

  • writes go through DnsUpdateService
  • DNSBL/FraudBL publication payloads are expanded by a dedicated DNSBL publication layer before the DNS update service runs
  • updates are sent directly to the DNS master via the DNS update layer
  • successful writes trigger zone sync handling
  • affected zone caches are synchronized or refreshed as needed after successful updates
  • delete operations require a specific target value to avoid dangerous full-RRset deletion

Common workflows and examples

1) Validate a token before doing anything else

Use GET /api/dnsbl/token/info when a helper client, plugin, or native tool needs to confirm whether the supplied token is:

  • active
  • add-capable
  • delete-capable
  • CIDR-delete-capable
  • a real DNSBL token or some other Tools token type

This is the fastest way to distinguish:

  • 401 no_token
  • 404 not found
  • 422 wrong_token_type
  • valid admin passthrough

2) Inspect first, then delist

Recommended flow for one IP:

  1. call POST /api/dnsbl/check-ip
  2. show the current live listing state to the operator
  3. if delist is appropriate, send one POST /api/dnsbl/records/delete request for that IP

Important: do not fan out one check-ip result into multiple delete calls for the same IP just because delete_candidates[] contains multiple publication families. The server-side delete flow already expands the live-listed owners/zones for that IP.

Example delete response for an already cleared IP:

{
  "ok": true,
  "reason": "already_not_listed",
  "already_not_listed": true,
  "forced_success": true,
  "operation_count": 0,
  "message": "No active DNSBL/FraudBL listings were found for this IP, so the delete request was accepted as a no-op."
}

3) Add or update one reputation record

Use the DNSBL endpoints when the caller has an IP address + bitmask and wants Tools to own:

  • reverse-owner naming
  • publication-family expansion
  • fraud mirroring
  • commerce mirroring

Example add request:

{
  "ip": "203.0.113.4",
  "bitmask": 64,
  "publication_type": "dnsbl",
  "ttl": 300
}

Example update request:

{
  "ip": "203.0.113.4",
  "old_bitmask": 64,
  "bitmask": 84,
  "publication_type": "fraudbl",
  "ttl": 300
}

4) Use dry-run before real bulk jobs

POST /api/dnsbl/records/add|delete|update|bulk accepts dry_run=true.

Use it when you want to validate:

  • payload shape
  • token scope
  • delete guardrails
  • publication expansion

without applying DNS writes.

5) Use the helper endpoints only for their dedicated jobs

  • GET /api/dnsbl/engine-settings is for dnsbl-engine-style runtime alignment
  • POST /api/dnsbl/forum/bounce is for rejected forum-recipient intake
  • POST /api/dnsbl/dmarc/report is for DMARC review ingestion, not direct blacklist publication

DNSBL publication payloads

When you use the DNSBL-oriented write flow, you send an IP address and a bitmask instead of constructing owner names yourself.

POST /api/dnsbl/records/add

{
  "ip": "1.2.3.4",
  "bitmask": 12,
  "publication_type": "commerce",
  "ttl": 300,
  "dry_run": true
}

This currently results in publication to both:

  • 4.3.2.1.bl.fraudbl.org
  • 4.3.2.1.ecom.fraudbl.org

with target:

127.0.0.12

Fraud/non-commerce adds now behave differently from commerce adds:

  • fraud-style publication (publication_type=fraud / fraudbl, or a bitmask containing IP_PHISHING) now mirrors into dnsbl.tornevall.org, opm.tornevall.org, and bl.fraudbl.org
  • commerce publication still stays in bl.fraudbl.org + ecom.fraudbl.org and is not mirrored into the ordinary DNSBL zones
  • add/update responses can now also include additive publication.publication_types[] metadata so clients can see which publication families were expanded for the submitted write

POST /api/dnsbl/records/delete

{
  "ip": "1.2.3.4",
  "bitmask": 12,
  "publication_type": "commerce",
  "dry_run": true
}

Delete requests remove the specific A record target from every owner generated by the DNSBL publication layer. For commerce-related entries that means both the ordinary FraudBL zone and the dedicated ecom zone.

Delete handling is now also removal-audited server-side. Real delete attempts, dry-runs, denied removals, and delete items inside bulk requests all emit dedicated DNSBL removal audit entries in the backend logs with the resolved IP, owner names, zones, and delete targets when that information is available.

Delete/write callers can now also add optional audit-tracing metadata when they want the backend audit trail to identify the originating system more clearly, for example:

{
  "source_type": "wordpress_plugin",
  "source_name": "Example Site",
  "source_site_url": "https://example.org/",
  "source_page_url": "https://example.org/removal/"
}

These fields are additive only. They are meant for operator/audit visibility and do not change the authoritative server-side delist resolution. When explicit source fields are missing, Tools can still derive partial origin details from request headers such as Origin, Referer, and User-Agent when those headers exist.

If the admin enables the Slack log category DNSBL removal audit, those dedicated removal events are also forwarded to Slack without requiring the broader API /dnsbl requests audit stream.

The same removal audits can now also be sent as plain-text mail to the configured DNSBL_REMOVAL_AUDIT_EMAIL recipient list. This is intended for operators who want a second audit sink in addition to logs/Slack.

POST /api/dnsbl/records/update

{
  "ip": "1.2.3.4",
  "old_bitmask": 4,
  "bitmask": 12,
  "publication_type": "commerce",
  "ttl": 300,
  "dry_run": true
}

Update requests require old_bitmask so the DNS update layer can safely delete the previous 127.0.0.X target before inserting the new one.

Token info endpoint

GET /api/dnsbl/token/info

Returns permission and scope information for a DNSBL token without requiring a logged-in session.

Auth: X-Dnsbl-Token: <token> header or ?dnsbl_token=<token> query parameter.

Returns info for any token status (active, pending, revoked) so clients can see the exact state and understand why a token may not be working.

The endpoint now performs live inspection for any non-empty token string. It no longer rejects a value only because it fails a local length/format check before lookup.

If the supplied value matches another Tools token/provider that belongs to an active admin-owned Tools token, the endpoint now treats it as having automatic effective DNSBL access and returns a success payload with token.resolved_via="admin_api_key_passthrough" plus full effective permission fields.

Example response:

{
  "ok": true,
  "token": {
    "name": "My Token",
    "status": "active",
    "is_admin_token": false,
    "allow_add": true,
    "allow_delete": false,
    "can_add": true,
    "can_delete": false,
    "scope_label": "add",
    "zones": ["dnsbl.tornevall.org", "opm.tornevall.org", "bl.fraudbl.org", "ecom.fraudbl.org"],
    "approved_at": "2026-04-04T12:00:00+00:00",
    "requested_by_email": "user@example.com"
  }
}

Error responses:

  • 401 – no token provided
  • 404 – token not found in the system
  • 422 – the supplied value matches another Tools token/provider instead of a DNSBL write token and it is not eligible for admin passthrough

Admin passthrough example:

{
  "ok": true,
  "message": "The supplied token belongs to a Tools admin and therefore has automatic DNSBL read/write access through the same X-Dnsbl-Token flow.",
  "token": {
    "name": "Tools AI Bearer",
    "status": "active",
    "is_admin_token": true,
    "allow_add": true,
    "allow_delete": true,
    "can_add": true,
    "can_delete": true,
    "scope_label": "admin_api_key_passthrough",
    "zones": ["dnsbl.tornevall.org", "opm.tornevall.org", "bl.fraudbl.org", "ecom.fraudbl.org"],
    "approved_at": null,
    "requested_by_email": "admin@example.com",
    "provider": "tools_ai_bearer",
    "resolved_via": "admin_api_key_passthrough",
    "is_admin_passthrough": true
  },
  "user": {
    "id": 1,
    "name": "Admin User",
    "email": "admin@example.com",
    "is_admin": true,
    "is_acknowledged_admin": true
  },
  "admin_owner": {
    "is_admin_user": true,
    "is_acknowledged_admin": true,
    "automatic_dnsbl_access": true,
    "accepted_for_dnsbl_reads_and_writes_via_token": true,
    "token_is_active": true
  }
}

Example 422 wrong_token_type response:

{
  "ok": false,
  "reason": "wrong_token_type",
  "message": "The supplied token is a valid Tools bearer token, but it is not a DNSBL write token.",
  "token_type": "tools_bearer",
  "provider": "tools_ai_bearer",
  "token": {
    "provider": "tools_ai_bearer",
    "name": "Tools AI Bearer",
    "is_active": true,
    "is_global": false,
    "is_personal": true,
    "user_id": 7
  },
  "user": {
    "id": 7,
    "name": "Example User",
    "email": "user@example.com",
    "is_admin": false,
    "is_acknowledged_admin": false
  }
}

Live IP inspection endpoint

POST /api/dnsbl/check-ip

Performs a live DNS-backed inspection for one IP address and returns the currently listed DNSBL/FraudBL publication families together with the delist candidates that the authenticated token/session can act on.

Auth: the same DNSBL auth model as the write endpoints (X-Dnsbl-Token, dnsbl_token, or privileged admin/session/API-key access).

Request:

{
  "ip": "1.2.3.4"
}

Example response:

{
  "ok": true,
  "ip": "1.2.3.4",
  "message": "Current DNS lookup found active listings and the authenticated context can delist: DNSBL (64).",
  "lookup": {
    "listed": true,
    "combined_bitmask": 64,
    "constants": ["IP_ABUSE_NO_SMTP"],
    "statement": "Current DNS lookup found active DNSBL/FraudBL listings for this IP.",
    "zones": [
      {
        "zone": "dnsbl.tornevall.org",
        "publication_type": "dnsbl",
        "host": "4.3.2.1.dnsbl.tornevall.org",
        "listed": true,
        "bitmask": 64,
        "target": "127.0.0.64",
        "constants": ["IP_ABUSE_NO_SMTP"]
      }
    ],
    "delete_candidates": [
      {
        "publication_type": "dnsbl",
        "bitmask": 64,
        "active_flags": ["IP_ABUSE_NO_SMTP"],
        "zones": ["dnsbl.tornevall.org", "opm.tornevall.org"]
      }
    ],
    "delete_candidate_count": 1
  },
  "token": {
    "auth_mode": "dnsbl_token",
    "has_token": true,
    "can_add": true,
    "can_delete": true,
    "can_update": true,
    "scope_label": "add_delete",
    "token_name": "My Token",
    "token_status": "active"
  }
}

Operational note: POST /api/dnsbl/check-ip and the current DNSBL write endpoints now always emit explicit backend logs and also forward DNSBL API activity to the configured audit/notification out-channels when those channels and matching rules are enabled.

DNSBL dry run acknowledgement

POST /api/dnsbl/records/add|delete|update|bulk now accepts optional dry_run.

  • dry_run=true validates payload, token scope, zone permissions, and publication expansion.
  • No DNS update is executed and no cache sync is applied.
  • Response includes dry_run: true and dry_run_accepted: true when the request is accepted.

Example response shape:

{
  "ok": true,
  "message": "Dry run accepted. No DNS updates applied.",
  "dry_run": true,
  "dry_run_accepted": true,
  "operation_count": 2
}

Delete guardrails (user-level)

DNSBL delete operations can now be constrained per token owner through admin-managed guardrails in /admin/dnsbl/tokens.

Current guardrail fields:

  • delete_min_cidr_prefix - delegated IPv4 CIDR delete floor for non-admin token owners (/24 broadest, /32 single-IP only, null = CIDR delete not delegated)
  • delete_limit_per_day - max delete units per day for the user
  • delete_cidr_limit - max expanded IP/delete targets accepted in one request
  • delete_throttle_limit
  • delete_throttle_window_seconds

Guardrails apply to POST /api/dnsbl/records/delete and bulk delete items in POST /api/dnsbl/records/bulk.

When blocked, the API returns explicit reasons:

  • delete_cidr_not_allowed (422)
  • delete_cidr_prefix_too_broad (422)
  • delete_daily_limit_exceeded (429)
  • delete_throttle_exceeded (429)
  • delete_cidr_limit_exceeded (422)

Additive delete_guardrails metadata is now included in DNSBL token-info and auth-summary responses so clients can show effective delete limits before sending delist requests. Token/auth metadata can now also include can_cidr_delete so helper clients can hide CIDR delete UI until delegated CIDR access is actually enabled.

TTL defaults in current write path

The active DNS update service uses a default TTL of 300 seconds for new records unless another TTL is supplied. That aligns with the intended low-TTL approach for fast-changing reputation data.

Bulk runtime note

Large DNSBL bulk writes can keep one HTTP request busy for a while because each submitted IP may still fan out into multiple DNS update operations server-side.

The current implementation now extends the runtime budget for those DNSBL bulk requests and also retries retryable interrupted UDP socket reads (for example socket_recvfrom(): Interrupted system call) before the write is finally treated as failed.


Commerce flow, removals, and TTL

Commerce flow

The intended commerce flow in the current ToolsAPI architecture is:

  1. A request enters through the ToolsAPI DNSBL / commerce flow.
  2. ToolsAPI hands the operation to the DNSBL/DNS handling layer.
  3. The DNSBL layer is responsible for encoding / anonymizing the entry if required.
  4. The DNS/DNS zone layer writes, updates, or removes the published DNS records.

This keeps anonymization and naming logic in one place.

Current encoding responsibility

The current implementation still uses reverse-IP owner encoding for the published DNS owner names.

That is intentional for now: the important architectural rule is that the caller does not construct those names manually. If anonymized hashing or another GDPR-driven encoding model is introduced later, that change belongs in the DNSBL publication layer rather than in clients or in the generic DNS endpoints.

Removals

For commerce-related removals, delete handling should cover both:

  • the regular bl.fraudbl.org entry
  • the dedicated ecom.fraudbl.org entry

TTL policy

Commerce-related records should use the shortest safe TTL in the DNS environment.

Recommended target:

300 seconds

Why this matters:

  • commerce-related classifications must be safe for merchants
  • stale fraud verdicts should not linger longer than necessary
  • removals should propagate quickly

WordPress DNSBL plugin behaviour

The current WordPress plugin is no longer driven by the old API v3 endpoint contract.

What the plugin does today

The current plugin code:

  • performs direct DNS lookups against configured resolvers
  • defaults to:
    • dnsbl.tornevall.org
    • bl.fraudbl.org
  • decodes the returned 127.0.0.X result into a bitmask
  • records request statistics locally in WordPress
  • stores lookup cache locally in WordPress
  • supports safe local testing through a whitelist model
  • now exposes one visible DNSBL / Tools API token field in the admin UI, where the live checker asks Tools directly and reports effective DNSBL access for the configured token
  • the public checker/removal flow now gives an immediate local DNS answer first and, when a token exists, follows it with a background call to POST /api/dnsbl/check-ip before the delete button is unlocked
  • that second-step Tools inspection still returns delist candidates grouped by publication family for UI/debugging, but the final delete request is now resolved authoritatively server-side from the submitted IP's live DNS state rather than trusting WordPress to choose the exact subzones itself
  • the plugin now also stamps its Tools write/check requests with additive site identity metadata (source_type, source_name, source_site_url, source_site_host) so DNSBL delist audits can show which WordPress site submitted the request even during server-to-server calls
  • when checker-mode Delist is clicked, the delist button itself now shows the in-flight submitting state while both checker buttons are disabled until the request finishes, which reduces accidental double submits
  • completed checker runs are now reusable without a page refresh: users can edit the IP field again immediately after a finished lookup, or click Reset to clear the current checker/CIDR/background state and start over
  • checker and delist requests now also show a dedicated busy spinner/status row below the action buttons so it is clearer that the live WordPress-side request is still running even before the result box updates
  • Turnstile on the public delisting/removal flow is now explicitly opt-in from the WordPress admin instead of being inherited automatically from comment/registration Turnstile settings, which lets operators disable only the removal-page challenge if Cloudflare has temporary issues
  • that same removal-page Turnstile now also has an optional fail-open checkbox: if the widget cannot initialize or Cloudflare verification has an operational outage, WordPress can temporarily bypass the challenge automatically for the public removal page until a later healthy Turnstile verification clears that bypass again
  • advanced CIDR checks for the public delist flow now stay inside WordPress instead of calling Tools for the block scan; the plugin walks the delegated CIDR range locally in small batches, shows live progress, and keeps a visible hit list of listed IPs found in the block while the scan runs
  • that local CIDR scan is intentionally paced in small batches so the resolver side is not flooded, while the final CIDR delete still goes through the DNSBL write endpoint once the plugin has a completed local scan ticket for the range, now only for the IPs that the local CIDR scan actually marked as listed, and now one IP at a time in sequence
  • if a user clicks Check if listed while a valid CIDR is still sitting in the initial checker IP field, WordPress now opens Advanced at that point, moves the CIDR there automatically, and treats that Advanced CIDR value as the authoritative scope for the later local scan and delist submit
  • delegated/non-admin tokens can now expose a configurable CIDR floor (for example /25../32) through delete_guardrails.delete_min_cidr_prefix; when that value is absent the public plugin keeps CIDR delete disabled and falls back to single-IP delist only
  • now validates the configured main delisting page against live token.can_delete access before WordPress accepts that page as the plugin-managed primary removal page
  • ships a built-in main removal-page template at templates/removal-page.php when the selected delisting page does not already contain one of the supported removal shortcodes
  • still supports custom WordPress removal pages through [dnsbl_removal_form] / [tornevall_dnsbl_removal_form], while hiding operations that the current token is not allowed to perform

Main removal page vs shortcode pages

  • The plugin-managed main removal page is delete-focused and only becomes active when the configured token currently has live delete / delist permission.
  • That activation check happens during the WordPress settings save flow and reuses the same GET /api/dnsbl/token/info permission data that the admin Check token permissions button already reads.
  • If the selected WordPress page already contains a supported shortcode, the plugin leaves that page content alone.
  • If the selected WordPress page does not contain a supported shortcode, the plugin injects its own built-in layout and renders the backend-connected delete form there automatically.
  • Custom shortcode pages remain valid for site owners who want their own copy, layout, or surrounding content.

Cache and cleanup

The current plugin cache model now uses:

  • default TTL: 600 seconds
  • minimum TTL: 300 seconds
  • default cleanup interval: 300 seconds

The plugin also includes scheduled cleanup of expired cache entries.

Statistics shown by the plugin

The admin UI now distinguishes between:

  • total cached entries
  • cached blacklisted entries
  • cached clear / non-listed entries

It also records live event statistics such as:

  • total checks
  • unique visitors
  • blacklist hits
  • blocked requests
  • unique blocked visitors
  • 24-hour snapshot

Whitelist model

Whitelisted IPs are still:

  • checked
  • counted in statistics
  • allowed to appear in testing
  • consulted during blacklist/spam-handling runs so matching exempt IPs can be removed instead of being left published in DNSBL zones

But they are not:

  • blocked
  • redirected
  • marked as spam by the plugin's blocking logic

This is the intended safe dry-run model for WordPress administration.

Current visitor safety

For new installs, the current visitor IP can be seeded into the whitelist when REMOTE_ADDR is available.

The admin UI also supports:

  • warning notices when the current admin IP would otherwise match active flags
  • a shortcut to add the current visitor address to the whitelist

Optional Tools integration

The plugin also contains an optional Tools-based comment assessment call to:

/api/tools/dnsbl/comment-assess

That path is treated as an optional Tools integration by the plugin. The public DNS documentation on this page focuses on the active DNS-backed read/write model in the current ToolsAPI codebase.


Legacy: historical DNSBL v5 / API v3 reference

Deprecated

Everything in this section is historical reference material only. It is no longer the active integration model for ToolsAPI.

Historical version note

The DNSBL backend used to be described as version 5.0, while the HTTP layer was described as API v2 or API v3.

Historical permissions

Legacy permission names included:

  • allow_cidr
  • allow_cidr_update
  • can_purge
  • dnsbl_update
  • fraudbl_update
  • global_delist
  • local_delist
  • overwrite_flags

Historical API v2 request style

Legacy v2 lookup requests used forms like:

POST https://api.tornevall.net/2.0/dnsbl/ip/
bulk[]=ipaddr1&bulk[]=ipaddr2

The older API also used overloaded suffix syntax for extended info and add/delete operations.

Historical API v3 request style

POST /3.0/dnsbl/

Used to resolve or check whether an IP was listed.

Example:

{"ip":"1.2.3.4"}

PUT /3.0/dnsbl/

Used to insert or update blacklisted IP data.

Example:

{
  "ip": {
    "10.10.10.10": 64
  },
  "type": "dnsbl"
}

DELETE /3.0/dnsbl/

Used to delist or remove an IP.

Example:

{
  "ip": "1.2.3.4"
}

Historical registration types

The old API described:

  • dnsbl
  • ecommerce

In that legacy implementation, commerce registration was already treated as a special case and the old codebase also referenced:

  • dnsbl.tornevall.org
  • bl.fraudbl.org
  • ecom.fraudbl.org

Historical flag note

Older API v3 code still named bit 8 as:

FREE_SLOT_8_PREVIOUSLY_PROXYTIMEOUT

That naming is legacy-only and should not be used in current specifications.


Related documentation

Last Updated: 2026-04-24