API Overview

SpamFoo Client exposes a local HTTP API for classifying messages, sending user corrections, managing a user's stored data, and checking service health. This page covers what every endpoint has in common. Each endpoint has its own page with request and response details.

Base URL and Port

Property Value
Base URL http://localhost:16253
Port 16253. In standalone mode, it can be changed with SPAMFOO_PORT.
Protocol HTTP. SpamFoo Client does not serve HTTPS.
Listen address localhost by default. Set SPAMFOO_BIND_ADDRESS to listen on another address. The Docker image listens on all interfaces (0.0.0.0).
Responses JSON with camelCase field names. Enum values are strings.
Contract version Every response has an X-SpamFoo-Contract header with the API's version, currently 1. See Versioning.

Authenticate Requests

SpamFoo Client checks callers with a shared secret called the proxy secret. Send it in the X-SpamFoo-Proxy-Auth header.

  • If you set the SPAMFOO_PROXY_SECRET environment variable, that value is the secret.
  • Otherwise the secret is read from etc/sf.proxysecret in the configuration directory, which is the data directory unless SpamFoo Client was started with --config-dir. Unless SmarterMail runs it, SpamFoo Client creates this file with a random value on first start. In the Docker image the file is /data/etc/sf.proxysecret, and with the Linux package it is /var/lib/spamfoo/etc/sf.proxysecret.
  • When SmarterMail runs SpamFoo Client, SmarterMail writes the secret and sends it on its own requests.

Without SmarterMail, the same value is the admin console password, so store it like a password.

Endpoints Secret Required
Classification: /classify-stream, /tabs/classify-stream In standalone mode, always. In embedded mode, only when the request does not come from loopback (localhost). A wrong or missing secret returns 401 with {"error": "unauthorized"}.
Corrections, personal data, settings, and integration: /feedback-stream, /tabs/feedback-stream, /personalization/..., /admin/api/settings, /integration/..., /generate-login-link Always, even from loopback. A wrong or missing secret returns 401 with an empty body.
Health: /health, /health/live, /health/ready Never.
  • Loopback means the connection comes from 127.0.0.1 or ::1 on the same machine. Every local process counts, including a reverse proxy running on the same host.
  • Docker: requests to the container's published port arrive from Docker's network, not loopback, so they need the secret.
  • Standalone mode trusts no caller by its address. See Mode.

When the Secret Changes

In standalone mode, an administrator can change the secret while SpamFoo Client runs. For 15 minutes after the change, SpamFoo Client still accepts the old secret on classification, correction, and /integration/... requests, so your integration has time to switch. Every other endpoint accepts only the new secret right away. Embedded mode reads the secret only when it starts.

Repeated Wrong Secrets

After too many wrong secrets from one IP address, SpamFoo Client answers requests from that address with 429 for a while, including health checks. Requests that carry the correct secret, or come from a signed-in dashboard, still get through, so a mail server behind the same reverse proxy keeps working. The body is {"error": "...", "code": "auth_blocked"}, and the Retry-After header gives the seconds left.

The code samples in this reference read the secret from the SPAMFOO_PROXY_SECRET environment variable on the calling machine.

Send the Message in the Request Body

Endpoints that work on a message take the raw message as the request body. The raw message, also called raw .eml content, is the full RFC 5322 message, headers and MIME body, exactly as it arrived over SMTP. Details that are not inside the message, such as the connecting IP address, go in X- request headers.

  • Send the bytes unchanged. Do not decode, re-encode, or wrap them in JSON.
  • Set Content-Type: message/rfc822. SpamFoo Client does not check this header on these endpoints, but it describes the body correctly.
  • An empty body returns 400 with {"error": "No email content provided"}.
  • The body size limit is set by SPAMFOO_MAX_CLASSIFY_BODY_BYTES. The default is 520 MiB, or 64 MiB in standalone mode. A larger body returns 413. Get Integration Info reports the limit in effect.
  • SpamFoo Client does not read message files from disk on your behalf. Always send the message in the body.

Endpoints that take JSON, such as the DELETE endpoints on Manage Personal Data, need Content-Type: application/json. Without it they return 415.

Identify the Mailbox and Message

These request headers describe who the message is for and how it arrived. Each endpoint page lists which ones it reads.

Header Meaning
X-User-ID The mailbox the message belongs to, usually the recipient's full email address. SpamFoo Client keeps each user's corrections under this ID, so send the same value for the same mailbox on every request, in both classification and correction calls. Without it, only the global models and system rules apply.
X-Sender-IP IP address of the server that connected to your mail server to deliver the message. Used for SPF checks and IP reputation.
X-Sender-Email The envelope sender, from the SMTP MAIL FROM command. Used for authentication checks and to match allow and block rules.
X-Recipient-Email The envelope recipient, from the SMTP RCPT TO command. Used to match domain-level allow and block rules and to check for impersonation of protected people.
X-SpamFoo-Log-ID Your own ID for the message, such as a queue ID. It appears in SpamFoo Client's classification log so you can match its log lines to yours. Up to 64 characters from A-Z a-z 0-9 . _ : -; any other value is ignored.

SpamFoo Client reads X- header values as UTF-8, so mailbox names with non-ASCII characters work. Make sure your HTTP client sends them as UTF-8. In .NET, HttpClient rejects non-ASCII header values unless you set SocketsHttpHandler.RequestHeaderEncodingSelector.

How Licenses Affect Requests

A SpamFoo license can include spam detection, Email Classification, or both. SpamFoo Client checks the license before it runs an endpoint.

Endpoint License Needs
/classify-stream Spam detection or Email Classification. /classify-stream runs only the parts the license includes. See Classify a Message.
/tabs/classify-stream Email Classification.
Corrections, personal data, and health No license check.

When the license does not include what an endpoint needs, the response is 403 with a body such as {"error": "feature_not_licensed", "features": ["classification"]}. The features list names what the endpoint accepts.

Errors and Status Codes

Most errors return a JSON body with an error field. Some add more fields, such as code, queueDepth, or features.

{
    "error": "Classification queue timeout",
    "code": "queue_timeout",
    "queueDepth": 12
}
Status Meaning
200 Success.
400 A required header, field, or body is missing or invalid, or the message could not be parsed. The error field says which. A missing required query parameter or unreadable JSON body returns 400 with no body.
401 The proxy secret is missing or wrong. See Authenticate Requests.
403 feature_not_licensed: the license does not include what this request needs.
413 request_body_too_large: the body is over the size limit. The response includes maxBytes.
415 A JSON endpoint was called without Content-Type: application/json.
429 auth_blocked: too many wrong secrets came from this address. See Repeated Wrong Secrets.
500 An unexpected error inside SpamFoo Client. Check the SpamFoo Client logs.
503 SpamFoo Client is busy. See Handle Busy Responses.

Handle Busy Responses

SpamFoo Client limits how much work it accepts at once. Every 503 has a Retry-After header, and the code field says why:

  • server_busy: too many requests were in progress. {"error": "server_busy", "code": "server_busy", "inFlight": 1024} with Retry-After: 1. The request was turned away before SpamFoo Client read the message. The limit is set by SPAMFOO_MAX_CLASSIFY_INFLIGHT. Requests marked as bulk have their own, smaller limit, add "priority": "bulk", and get Retry-After: 2. See Mark Bulk Work.
  • queue_timeout: the request waited too long for a free classification slot. {"error": "Classification queue timeout", "code": "queue_timeout", "queueDepth": 12}. queueDepth is how many requests were waiting.
  • deadline_exceeded: only for requests that send X-SpamFoo-Deadline-Ms, when the wait for a slot would be longer than the deadline. See Set a Deadline.
  • not_ready or shutting_down: from /health/ready, when SpamFoo Client can't classify mail yet or is stopping. See Check Health.

During mail delivery, treat a 503 as "no verdict" and deliver the message normally rather than holding it. Retry only when your process can wait. To see how close SpamFoo Client runs to these limits, read the queue counters from Check Health.

Endpoints

Method Path Purpose
POST /classify-stream Get a spam verdict and Email Classification. See Classify a Message.
POST /tabs/classify-stream Get an Email Classification only. See Classify Without Spam Checks.
POST /feedback-stream Report that a user marked a message as spam or not spam. See Report Spam or Not Spam.
POST /tabs/feedback-stream Report that a user moved a message to a different Email Classification. See Report a Classification Fix.
DELETE /personalization/data
/personalization/tabs/data
Delete a user's stored corrections. See Manage Personal Data.
GET /personalization/knn/stats
/personalization/tabs/knn/stats
Count a user's stored corrections. See Manage Personal Data.
GET /health
/health/live
/health/ready
Check service status, readiness, and queue load. See Check Health.
GET /integration/info Read the version, mode, license, and limits of SpamFoo Client. See Get Integration Info.
GET
PUT
/integration/address-map Send or read the map of mailboxes and the addresses that deliver to them. See Push an Address Map.
DELETE /integration/users/{userId} Delete everything stored for a user when their mailbox is removed. See Delete a User.
POST /generate-login-link Make a one-time dashboard sign-in link. See Create a Sign-In Link.
GET
PUT
/admin/api/settings Read or change SpamFoo Client's settings. See Read and Change Settings.

Versioning

The API has one version number, sent on every response in the X-SpamFoo-Contract header, and in the body of /health/ready and /integration/info as contract. The current version is 1.

  • Within a version, changes only add things: new optional request headers, new response fields, and new endpoints. Ignore response fields you don't know.
  • Removing something, or changing what an existing header or field means, raises the version.