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.

  1. Log in, go to My account, and make a key under API keys. Copy it: it is shown only once.
  2. Preferably, limit the key to the IP addresses of the system that uses it.
  3. 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." } }
StatusCodeMeaning
401unauthenticatedNo key, or a key that is wrong or deleted.
403forbiddenNot allowed, for example from an IP address the key is not for.
404not_foundDoes not exist, or you may not see it. The API never says which.
409exists, conflict, release_failedAlready exists, still in use, or already released.
422invalidThe input is not valid. The message says what is wrong.
429rate_limitedToo many requests. Wait a moment (see the Retry-After header).
502, 503unavailable, ...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.

FieldTypeDescription
domaintextRequired. For example example.com.
dest_hosttextThe mail server to deliver to. Default mail.<domain>.
dest_portnumberDefault 25.
greylistingbooleanDefault false.
activebooleanDefault true. Inactive domains get no mail through the filter.
user_idslistYour customers that get access to this domain. Any other id refuses the whole request.
owner_idnumberAdministrators 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.

FieldTypeDescription
emailtextRequired. Also the login name.
passwordtextRequired, at least 10 characters.
nametext
roletextRequired: customer or mail.
domain_idslistCustomers: the domains they may see.
addresseslistMail users: the addresses they may see, in your domains.
can_manage_usersbooleanCustomers: may make users of their own.
can_trainbooleanMail users: may mark mail as spam or not spam. Default true; at most 100 messages a day.
activebooleanDefault true.
parent_idnumberAdministrators 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.

FieldTypeDescription
daysnumberThe last 1 to 365 days. Default 7.
qtextSearch the sender, From, subject, recipient, IP address or Message-ID.
scopetextOnly one of your domains or addresses. Default: everything you may see.
pagenumberPage, from 1.
per_pagenumber1 to 100. Default 25.
statustextblocked, 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.

FieldTypeDescription
daysnumberThe last 1 to 365 days. Default 7.
qtextSearch the sender, From, subject, recipient, IP address or Message-ID.
scopetextOnly one of your domains or addresses. Default: everything you may see.
pagenumberPage, from 1.
per_pagenumber1 to 100. Default 25.
statustextquarantined 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.

FieldTypeDescription
learnbooleanAlso teach the filter that this is not spam.
againbooleanAlready 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.
tolistDeliver 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.

FieldTypeDescription
actiontextrelease, spam or ham (not spam).
idslistQuarantine 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.