Content Filter API
Introduction
The Content Filter API is a high-performance mail content filtering service that checks emails for spam, phishing, malware and other unwanted mail. It provides endpoints for checking emails, training the Bayes classifier, and querying historical mail check requests.
Authentication
All API endpoints require authentication via an API key passed in the Authorization header as a Bearer token:
Authorization: Bearer <API_KEY>
Production External
Mail (Collapsed)
Mail content filtering and classification endpoints.
Check Mail
Check an email for spam, phishing, malware and other unwanted mail. The email content must be provided as a
base64-encoded string in the body field. The response gives the action to take (accept or reject), the
class of the email and a confidence.
Testing the Filters
You can verify your integration end-to-end using well-known, deterministic test signals that flow through the real filtering pipeline:
-
Anti-malware: attach the EICAR test file
(
eicar.com) to the email. The malware scanner detects it and returns arejectverdict. -
Anti-spam: include a URL such as
https://www.dbltest.comin the email body. The Spamhaus DBL test domain triggers arejectverdict from the spam filter.
Body·
The email to check, including SMTP metadata and the base64-encoded email body.
Request body for checking an email against spam filters.
- Type: string • base64bodyrequired
The raw email content, base64-encoded. The email may be up to 50MiB (52,428,800 bytes) once decoded; a larger one is rejected with a 400. The whole request body is limited to 75MiB (78,643,200 bytes), which holds a 50MiB email in base64 plus the other request fields.
- Type: stringclient
_ip requiredThe client IP address of the mail sender.
- Type: stringhelorequired
The HELO/EHLO string from the SMTP transaction.
- Type: array of string 1…rcptsrequired
An array of recipient email addresses (minimum 1).
- Type: stringbias
_db _id min length:1max length:64The per-user Bayes classifier learns which words and other tokens are typical of the mail you label as spam or ham through
POST /mail/learn, and its verdict feeds into thePOST /mail/checkdecision (shown asanalysis.ml.useronGET /mail/requests/{request_id}).bias_db_idselects which of your Bayes token databases it uses. Tokens written byPOST /mail/learnare stored in the database identified bybias_db_id, andPOST /mail/checkreads tokens from the same database when scoring.Use it to keep separate Bayes databases for your own end users - for example, pass each end user's UUID so their training never influences anyone else's results. You can also switch to a new value to start from scratch when a database's predictions become unreliable, for example when its training data is suspected to be corrupted or poisoned; the old database is left untouched.
1-64 characters of
A-Z,a-z,0-9,.,:or-. The value is case-sensitive and used exactly as sent, soABCandabcselect different databases - always send a given identifier in the same case.Each database needs 200 learned samples before it contributes to scoring, so splitting your training across many databases delays that point for each of them.
Optional. Defaults to
0when omitted. Do not send together with the deprecatedbias_db_version; supplying both is rejected with a 400.bias_db_version: 3andbias_db_id: "3"select the same database, so you can switch fields without losing existing training data. - Type: integerFormat: int64bias
_db _version min:0deprecatedDeprecated - use
bias_db_id, which accepts string identifiers such as UUIDs.Integer form of
bias_db_id:bias_db_version: 3selects the same Bayes token database asbias_db_id: "3". Still accepted, but must be a JSON integer - a string value is rejected with a 400 that points tobias_db_id. Do not send together withbias_db_id.Optional. Defaults to
0when omitted. - Type: stringenumflow
The direction of email flow (inbound or outbound) is used to ensure only relevant rules are applied. If omitted, inbound is assumed.
values- inbound
Mail arriving at the protected system; inbound rules are applied.
- outbound
Mail leaving the protected system; outbound rules are applied.
- inbound
- Type: stringmail
_from The sender email address.
- Type: stringrdns
The reverse DNS hostname of the sending IP.
Responses
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
curl https://content-filter.spamhaus.com/api/v1/mail/check \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN' \
--data '{
"client_ip": "192.168.1.100",
"mail_from": "sender@example.com",
"helo": "mail.example.com",
"rdns": "mail.example.com",
"rcpts": [
"recipient@example.com"
],
"bias_db_id": "0b8f3c1e-7d2a-4f6b-9a51-3e2c8d4f7a90",
"flow": "inbound",
"body": "RnJvbTogc2VuZGVyQGV4YW1wbGUuY29tDQpUbzogcmVjaXBpZW50QGV4YW1wbGUuY29tDQo="
}'
{
"action": "accept",
"class": "ham",
"request_id": "3c159060-00e4-4320-a434-adddf323562b",
"diagnostic": "U0hfRElBR19FWEFNUEXF...",
"confidence": 0.93,
"scan_duration_ms": 26
}The mail check completed successfully.
Learn Mail
Submit a labelled email sample to train the per-user Bayes classifier. The email content must be provided
as a base64-encoded string in the body field. The class field indicates whether the sample is spam or ham.
Both learned and duplicate responses are successful outcomes (HTTP 200):
learned: The sample was incorporated into the Bayes classifier.duplicate: The sample was already known and the classifier was unchanged.
Body·
The labelled email sample to learn.
Request body for submitting a labelled email sample to train the per-user Bayes classifier.
Supply exactly one of body or diagnostic - never both, never neither:
-
bodycarries a full raw email to train on. Use this when uploading an email sample manually. -
diagnosticcarries the opaquediagnosticvalue returned by a previousPOST /mail/checkcall. The server reconstructs a skeleton email from the embedded tokens and feeds that to the trainer, so the original raw email does not need to be retained.
A request that supplies both fields, or neither, is rejected with a 400
(exactly one of 'body' or 'diagnostic' must be supplied).
- Type: stringenumclassrequired
The classification label for the email sample.
values- spam
Train the sample as spam (unwanted mail).
- ham
Train the sample as ham (legitimate mail).
- spam
- Type: stringbias
_db _id min length:1max length:64The per-user Bayes classifier learns which words and other tokens are typical of the mail you label as spam or ham through
POST /mail/learn, and its verdict feeds into thePOST /mail/checkdecision (shown asanalysis.ml.useronGET /mail/requests/{request_id}).bias_db_idselects which of your Bayes token databases it uses. Tokens written byPOST /mail/learnare stored in the database identified bybias_db_id, andPOST /mail/checkreads tokens from the same database when scoring.Use it to keep separate Bayes databases for your own end users - for example, pass each end user's UUID so their training never influences anyone else's results. You can also switch to a new value to start from scratch when a database's predictions become unreliable, for example when its training data is suspected to be corrupted or poisoned; the old database is left untouched.
1-64 characters of
A-Z,a-z,0-9,.,:or-. The value is case-sensitive and used exactly as sent, soABCandabcselect different databases - always send a given identifier in the same case.Each database needs 200 learned samples before it contributes to scoring, so splitting your training across many databases delays that point for each of them.
Optional. Defaults to
0when omitted. Do not send together with the deprecatedbias_db_version; supplying both is rejected with a 400.bias_db_version: 3andbias_db_id: "3"select the same database, so you can switch fields without losing existing training data. - Type: integerFormat: int64bias
_db _version min:0deprecatedDeprecated - use
bias_db_id, which accepts string identifiers such as UUIDs.Integer form of
bias_db_id:bias_db_version: 3selects the same Bayes token database asbias_db_id: "3". Still accepted, but must be a JSON integer - a string value is rejected with a 400 that points tobias_db_id. Do not send together withbias_db_id.Optional. Defaults to
0when omitted. - Type: string • base64body
The raw email content, base64-encoded. The email may be up to 50MiB (52,428,800 bytes) once decoded; a larger one is rejected with a 400. The whole request body is limited to 75MiB (78,643,200 bytes). Mutually exclusive with
diagnostic. - Type: stringdiagnostic
The opaque diagnostic value returned by a previous
POST /mail/check. The server reconstructs a skeleton email from the embedded tokens and trains the Bayes classifier on that. Mutually exclusive withbody.
Responses
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
curl https://content-filter.spamhaus.com/api/v1/mail/learn \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN' \
--data '{
"class": "spam",
"bias_db_id": "0b8f3c1e-7d2a-4f6b-9a51-3e2c8d4f7a90",
"body": "RnJvbTogc2VuZGVyQGV4YW1wbGUuY29tDQpUbzogcmVjaXBpZW50QGV4YW1wbGUuY29tDQo=",
"diagnostic": "U0hfRElBR19FWEFNUEXF..."
}'
{
"action": "learned",
"request_id": "51e9caa0-27b2-407f-acb2-9d453d96eab2"
}The learn request completed successfully.
Get Request
Returns the full details of a single mail check request, including an analysis of what the filter observed about the message.
Analysis
The analysis object explains the verdict in terms of documented codes rather than internal rule
names, so it can be displayed or acted upon without any knowledge of the filtering engine. It has
three parts.
Findings
analysis.findings lists what the filter observed about the connection and about the message's
authentication, one entry per observation. Each entry carries:
code- a stable identifier of the observation; every code is documented on theFindingschema.-
category-connection(the sending network and IP, its sending history, the HELO and reverse-DNS checks) orauthentication(SPF, DKIM, DMARC and ARC). severity- how to read it:good,info,warningorbad.
Findings are returned in a fixed display order. Where several checks describe the same fact on a
scale (the network's reputation, the IP's sending history) only the applicable tier is reported, and
a correct forward-confirmed reverse DNS is reported as the single helo_fcrdns_match finding rather
than its component checks. New codes may be added over time - treat an unknown code as informational.
Listings
analysis.listings has one entry for each element of the message that was found on a Spamhaus
dataset:
-
element- which part of the message was listed (the sending IP, a URL in the body, the sender domain, an attachment, ...). dataset- the Spamhaus dataset it was listed on (ZEN, DBL, HBL, ...).-
tag- an optional refinement: the reason category for DBL and DBLAL listings, the verdict for HBL attachment listings. value- the listed value itself, which can be looked up at https://check.spamhaus.org.-
origin- where the value was derived from when it is not directly visible in the message, for example the URL a listed hash was computed over.
tag and origin are omitted when absent. element and dataset are omitted for a listing this
API does not yet know how to describe; the value is still reported.
Machine learning
analysis.ml reports the machine-learning verdicts as confidences in the range 0..1, rounded to two
decimal places: global for the shared Bayes classifier, user for your per-user Bayes classifier
trained through POST /api/v1/mail/learn, and fuzzy for matches against fingerprints of known spam
or known legitimate mail. Each carries a spam and/or a ham side; a side is omitted when the
classifier did not lean that way, and an empty object means it produced no verdict.
Score
analysis.score is the filter's overall score for the message, normalised into the range 0..15 -
higher means more spam-like. It summarises the findings, listings and machine-learning verdicts into
one figure and is omitted when the filter recorded no score for the request.
The verdict itself is carried by action and class, its confidence by confidence, and the
training payload for POST /api/v1/mail/learn by diagnostic.
Path Parameters
- Type: stringFormat: uuidrequest
_id requiredThe unique identifier of the mail check request (UUID v4).
Responses
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
curl https://content-filter.spamhaus.com/api/v1/mail/requests/51e9caa0-27b2-407f-acb2-9d453d96eab2 \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN'
{
"request_id": "51e9caa0-27b2-407f-acb2-9d453d96eab2",
"event_time": "2026-03-06T18:00:00Z",
"action": "accept",
"class": "clean",
"confidence": 0.74,
"client_ip": "192.168.1.100",
"mail_from": "sender@example.com",
"helo": "mail.example.com",
"rdns": "mail.example.com",
"rcpts": [
"recipient@example.com"
],
"message_id": "20261013.abc123@example.com",
"analysis": {
"findings": [
{
"code": "network_reputation_good",
"category": "connection",
"severity": "good"
},
{
"code": "sending_history_very_good",
"category": "connection",
"severity": "good"
},
{
"code": "helo_fcrdns_match",
"category": "connection",
"severity": "good"
},
{
"code": "spf_pass",
"category": "authentication",
"severity": "good"
},
{
"code": "dkim_pass",
"category": "authentication",
"severity": "good"
},
{
"code": "dmarc_pass",
"category": "authentication",
"severity": "good"
}
],
"listings": [],
"ml": {
"global": {
"ham": 0.98
},
"user": {},
"fuzzy": {}
},
"score": 1.5
},
"diagnostic": "U0hfRElBR19FWEFNUEXF..."
}The request was found and returned successfully.
Search Requests
Search historical mail check requests. Returns request-level summaries (no per-rule details). Results are ordered
by event_time descending.
Time filtering: Use either relative for a sliding time window (e.g. 1h, 24h) or start/end for an
absolute range. These are mutually exclusive. If neither is provided, defaults to the last 24 hours.
Pagination: Use cursor-based pagination. The response includes a next_cursor field when more results exist;
pass its value as the cursor query parameter to fetch the next page. Keep all other query parameters (time range
and filters) identical across page requests - the cursor encodes only a position in the result order, not your
query. Changing the time range or filters while paging yields undefined page boundaries; start a new search instead.
Query Parameters
- Type: stringrelative
Time window relative to now. Supports Go duration strings plus
d(days) andw(weeks) suffixes (e.g.30s,5m,1h,24h,2d,1w). Maximum 30 days. Mutually exclusive withstart/end. If neitherrelativenorstart/endis provided, defaults to the last 24 hours. - Type: stringFormat: date-timestart
Start of absolute time window (RFC3339). Must be paired with
end. Mutually exclusive withrelative. - Type: stringFormat: date-timeend
End of absolute time window (RFC3339). Must be paired with
start. Mutually exclusive withrelative. - Type: stringenumclass
Filter results by email classification.
values- ham
Legitimate mail with no more specific class.
- spam
Unsolicited bulk mail.
- bounce
Bounce messages (NDRs, DSNs).
- clean
Good, legitimate mail - includes person-to-person.
- commercial
Mass commercial mail (newsletters etc).
- community:mailinglist
Mailing list (aka Listserv) discussions.
- community:social
Updates from social networks.
- malware
Malspam - links to or attachments with malicious content.
- ham
- Type: stringenumaction
Filter results by action taken.
values- accept
The email passed the spam and content checks and should be delivered.
- reject
The email was flagged as spam or malicious and should not be delivered.
- accept
- Type: numberFormat: floatmin
_confidence min:0max:1Only return requests whose verdict confidence (the
confidencefield, 0..1) is at least this value. Confidence is in the verdict itself, whichever class it is: a confident ham and a confident spam both pass a high minimum. Requests with no confidence (confidence: null) never match a confidence filter. - Type: numberFormat: floatmax
_confidence min:0max:1Only return requests whose verdict confidence (the
confidencefield, 0..1) is at most this value - for example0.6to find borderline verdicts worth reviewing. Requests with no confidence (confidence: null) never match a confidence filter. - Type: stringclient
_ip Filter by client IP address of the mail sender.
- Type: stringmail
_from Filter by sender email address (MAIL FROM).
- Type: stringhelo
Filter by the HELO/EHLO string presented by the sending MTA.
- Type: stringrdns
Filter by the reverse DNS hostname of the sending IP.
- Type: stringrcpt
Filter by a single recipient email address. Matches requests that include this address in their recipients list.
- Type: stringmessage
_id Filter by the email's
Message-IDheader, without the surrounding angle brackets. Matches exactly (case-sensitive). Only requests checked after Message-ID capture was introduced carry one, and the same message delivered to several recipients can match several requests. - Type: stringenumflow
Filter by the direction of the email, as given in the
flowfield of the check request.values- inbound
Mail arriving at the protected system.
- outbound
Mail leaving the protected system.
- inbound
- Type: integerFormat: int64limitmin:1max:1000
Maximum number of results to return.
- Type: stringcursor
Opaque pagination cursor returned as
next_cursorin the previous response. Omit for the first page.
Responses
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
curl 'https://content-filter.spamhaus.com/api/v1/mail/requests?relative=1h&start=2026-03-06T17%3A00%3A00Z&end=2026-03-06T18%3A00%3A00Z&class=ham&action=accept&min_confidence=0.9&max_confidence=0.6&client_ip=192.168.1.100&mail_from=sender%40example.com&helo=mail.example.com&rdns=mail.example.com&rcpt=recipient%40example.com&message_id=20261013.abc123%40example.com&flow=inbound&limit=100&cursor=MjAyNi0wMy0wNlQxODowMDowMFp8NTFlOWNhYTAtMjdiMi00MDdmLWFjYjItOWQ0NTNkOTZlYWIy' \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN'
{
"meta": {
"next_cursor": "MjAyNi0wMy0wNlQxODowMDowMFp8NTFlOWNhYTAtMjdiMi00MDdmLWFjYjItOWQ0NTNkOTZlYWIy"
},
"results": [
{
"request_id": "6c93ff8e-063f-4c39-b22f-b341de23a7c0",
"event_time": "2026-03-06T18:00:01Z",
"action": "reject",
"class": "spam",
"confidence": 0.97,
"client_ip": "192.168.1.100",
"mail_from": "sender@example.com",
"helo": "mail.example.com",
"rdns": "mail.example.com",
"rcpts": [
"recipient@example.com"
]
},
{
"request_id": "51e9caa0-27b2-407f-acb2-9d453d96eab2",
"event_time": "2026-03-06T18:00:00Z",
"action": "accept",
"class": "clean",
"confidence": 0.82,
"client_ip": "192.168.1.100",
"mail_from": "sender@example.com",
"helo": "mail.example.com",
"rdns": "mail.example.com",
"rcpts": [
"recipient@example.com"
]
}
]
}The search completed successfully.