SpamFactor API documentation
Manage domains and users, look up messages, release them and train the filter, from your own systems: a billing system, a control panel or a script.
Getting started
The API is for administrators and resellers. Every request acts as you, with exactly the access you have in the dashboard: a reseller sees and manages only their own domains and customers.
- Log in, go to My account, and make a key under API keys. Copy it: it is shown only once.
- Preferably, limit the key to the IP addresses of the system that uses it.
- Send the key with every request:
curl -H "Authorization: Bearer sf_key_..." https://www.spamfactor.com/api/v1/me
Keep the key secret, like a password. If it leaks, delete it on your account page: it stops working at once. A key also stops working when your account is deactivated.
Requests and answers
- Base URL:
https://www.spamfactor.com/api/v1
- Send bodies as JSON, with
Content-Type: application/json. Send Accept: application/json too.
- Answers are JSON:
{"data": ...}. Lists also have "meta" with page, per_page, total and pages.
- Times are ISO 8601 with time zone, for example
2026-10-08T16:40:58+02:00.
PATCH changes only the fields you send.
- At most 300 requests per minute per key.
- Changes are recorded in our audit log under your name, with the name of the key.
{
"data": [ ... ],
"meta": { "page": 1, "per_page": 25, "total": 120, "pages": 5 }
}
Errors
Errors have a code for your program and a message you can show to people as is:
{ "error": { "code": "invalid", "message": "Please enter a valid domain name." } }
| Status | Code | Meaning |
| 401 | unauthenticated | No key, or a key that is wrong or deleted. |
| 403 | forbidden | Not allowed, for example from an IP address the key is not for. |
| 404 | not_found | Does not exist, or you may not see it. The API never says which. |
| 409 | exists, conflict, release_failed | Already exists, still in use, or already released. |
| 422 | invalid | The input is not valid. The message says what is wrong. |
| 429 | rate_limited | Too many requests. Wait a moment (see the Retry-After header). |
| 502, 503 | unavailable, ... | The filter could not be reached. Try again later. |
Your account
GET/me
Who you are
Your role, what you may do, and the domains you may see.
curl -H "Authorization: Bearer sf_key_..." https://www.spamfactor.com/api/v1/me
{
"data": {
"id": 7,
"email": "you@example.com",
"role": "reseller",
"can_manage_users": true,
"can_manage_domains": true,
"can_train": true,
"domains": ["example.com", "example.net"]
}
}
Domains
The filter accepts mail for a domain from the moment it is added, and delivers it to its destination server. Point the MX records of the domain to mx1.spamfactor.com. Changes are live at once.
GET/domains
List your domains
Add ?check_mx=1 to also check the MX records (up to 50 domains).
curl -H "Authorization: Bearer sf_key_..." "https://www.spamfactor.com/api/v1/domains?check_mx=1"
GET/domains/{id}
One domain
With its users and an MX check: ok, partial, other or none.
POST/domains
Add a domain
Resellers always own the domains they add.
| Field | Type | Description |
|---|
domain | text | Required. For example example.com. |
dest_host | text | The mail server to deliver to. Default mail.<domain>. |
dest_port | number | Default 25. |
greylisting | boolean | Default false. |
active | boolean | Default true. Inactive domains get no mail through the filter. |
user_ids | list | Your customers that get access to this domain. Any other id refuses the whole request. |
owner_id | number | Administrators only: the reseller that owns it, or 0 for none. |
curl -X POST https://www.spamfactor.com/api/v1/domains \
-H "Authorization: Bearer sf_key_..." -H "Content-Type: application/json" \
-d '{"domain": "example.com", "dest_host": "mail.example.com", "user_ids": [12]}'
PATCH/domains/{id}
Change a domain
The same fields as above, except domain: a domain name cannot be changed. user_ids replaces your customers that have access; customers of others keep theirs. Without user_ids, the users stay as they are.
curl -X PATCH https://www.spamfactor.com/api/v1/domains/5 \
-H "Authorization: Bearer sf_key_..." -H "Content-Type: application/json" \
-d '{"greylisting": true}'
DELETE/domains/{id}
Delete a domain
The filter refuses mail for it at once. Answers 204 without body.
Users
Users log in to the dashboard. You only see and manage the users below you, and you can only give them access to what you may see yourself.
- customer: sees whole domains, can train the filter, and optionally make users of their own.
- mail: sees only their own addresses, can release, and can train the filter unless you turn that off (
can_train).
- admin and reseller: only administrators can make these.
Ids of users, domains or owners you may not use are never skipped silently: the whole request is refused with 422, and nothing is changed.
GET/users
List your users
Filter with ?role=customer, ?q= (part of the email address or name) or ?parent_id=. Paginated.
GET/users/{id}
One user
With their domains, addresses, parent and whether they use two-factor authentication.
POST/users
Create a user
The user can log in at once. Users you make belong to you.
| Field | Type | Description |
|---|
email | text | Required. Also the login name. |
password | text | Required, at least 10 characters. |
name | text | |
role | text | Required: customer or mail. |
domain_ids | list | Customers: the domains they may see. |
addresses | list | Mail users: the addresses they may see, in your domains. |
can_manage_users | boolean | Customers: may make users of their own. |
can_train | boolean | Mail users: may mark mail as spam or not spam. Default true; at most 100 messages a day. |
active | boolean | Default true. |
parent_id | number | Administrators only: the reseller or customer this user belongs to. |
curl -X POST https://www.spamfactor.com/api/v1/users \
-H "Authorization: Bearer sf_key_..." -H "Content-Type: application/json" \
-d '{"email": "jan@example.com", "password": "a-long-password", "role": "customer", "domain_ids": [5]}'
PATCH/users/{id}
Change a user
Only the fields you send. Without domain_ids or addresses, those stay as they are. Leave out password to keep it. Set active to false to block someone without deleting them.
DELETE/users/{id}
Delete a user
Not possible while they still have users of their own (409). Answers 204 without body.
POST/users/{id}/reset-2fa
Reset two-factor authentication
For a user who lost their phone: removes their authenticator app, passkeys and recovery codes, so they can log in with just their password and set it up again.
Messages
Every message the filter handled for your domains, including refused and delivered mail. Message details are kept for 3 months.
GET/messages
List messages
Newest first. A message that has a stored copy has a quarantine_id.
| Field | Type | Description |
|---|
days | number | The last 1 to 365 days. Default 7. |
q | text | Search the sender, From, subject, recipient, IP address or Message-ID. |
scope | text | Only one of your domains or addresses. Default: everything you may see. |
page | number | Page, from 1. |
per_page | number | 1 to 100. Default 25. |
status | text | blocked, tagged, deferred, delivered or virus. |
curl -H "Authorization: Bearer sf_key_..." "https://www.spamfactor.com/api/v1/messages?days=1&status=blocked"
{
"data": [{
"id": "1791470458-15666056831403781299",
"received_at": "2026-10-08T16:40:58+02:00",
"sender": "offers@example.ru",
"recipients": ["sales@example.com"],
"subject": "Special offer just for you!",
"score": 9.6,
"status": "blocked",
"quarantine_id": "ca5a1450-6b84-4c5b-9449-5b027e2b0d58"
}],
"meta": { "page": 1, "per_page": 25, "total": 1, "pages": 1 }
}
GET/messages/{id}
One message
Everything the filter found: the rules with their scores, DKIM, SPF, DMARC and Bayes, and the stored copy when there is one.
Quarantine
Copies of blocked and tagged spam, kept for 30 days. Copies of delivered mail are kept for 7 days so they can still be marked as spam; those are not in this list, and can only be delivered again with again.
GET/quarantine
List quarantined messages
Newest first.
| Field | Type | Description |
|---|
days | number | The last 1 to 365 days. Default 7. |
q | text | Search the sender, From, subject, recipient, IP address or Message-ID. |
scope | text | Only one of your domains or addresses. Default: everything you may see. |
page | number | Page, from 1. |
per_page | number | 1 to 100. Default 25. |
status | text | quarantined or released. |
GET/quarantine/{id}
One quarantined message
With its headers, text body, and per recipient whether it was released. delivery says per recipient what their mail server answered (sent, deferred, bounced or expired, with the answer), for the message itself and for every release in releases.
GET/quarantine/{id}/body
The message as HTML page
For showing the message. Scripts, forms and remote images are blocked.
GET/quarantine/{id}/raw
The original message
As .eml file (message/rfc822).
POST/quarantine/{id}/release
Release a message
Delivers it to the recipients you may see. Released mail is not scanned again, so only release what you trust.
| Field | Type | Description |
|---|
learn | boolean | Also teach the filter that this is not spam. |
again | boolean | Already released to every recipient, or a copy of delivered mail? Deliver it once more, for example when their mail server lost it. Possible as long as the copy is kept. |
to | list | Deliver to these addresses instead of the original recipients (a list, or comma separated). Only addresses on your own active domains, at most 20. |
curl -X POST https://www.spamfactor.com/api/v1/quarantine/ca5a1450-6b84-4c5b-9449-5b027e2b0d58/release \
-H "Authorization: Bearer sf_key_..." -H "Content-Type: application/json" -d '{"learn": true}'
POST/quarantine/bulk
Release or train several messages
At most 100 at once. Answers how many were done, and why the others were not.
| Field | Type | Description |
|---|
action | text | release, spam or ham (not spam). |
ids | list | Quarantine ids. |
{ "data": { "done": 9, "failed": 1, "errors": { "This message could not be found.": 1 }, "message": "..." } }
Training
Tell the filter when it was wrong. It learns for every customer, so it gets better for everyone.
POST/quarantine/{id}/learn/spam
This is spam
Works for quarantined messages and for stored copies of delivered mail (the quarantine_id of a message).
POST/quarantine/{id}/learn/ham
This is not spam
This does not deliver the message: use release for that.
Messages from a mailbox
For spam that is no longer stored: send the message itself, as .eml file (at most 10 MB). It is only accepted when it came through SpamFactor to one of your addresses, which is checked with its Message-ID. Files are not stored.
POST/uploads/check
Which message is this?
Finds the message without learning anything.
POST/uploads/learn/spam
This message from a mailbox is spam
Send the message as body with Content-Type: message/rfc822, or as multipart file file. Use uploads/learn/ham for not spam.
curl -X POST https://www.spamfactor.com/api/v1/uploads/learn/spam \
-H "Authorization: Bearer sf_key_..." -H "Content-Type: message/rfc822" --data-binary @message.eml
Statistics
GET/statistics
Totals and trends
Totals per status, a row per day, per domain, and the top rules, spam senders and countries. Takes days (default 30) and scope.
curl -H "Authorization: Bearer sf_key_..." "https://www.spamfactor.com/api/v1/statistics?days=30&scope=example.com"
Questions about the API? Email info@spamfactor.com.