Configuration reference
SpeculaPro is configured entirely through environment variables. There is no configuration file. Every variable starts with SP_.
How you set them depends on how you run SpeculaPro:
| You run SpeculaPro with | Put settings in |
|---|---|
make run (development) |
.env in the repository root. The Makefile loads it automatically |
| systemd | Environment= lines, or an EnvironmentFile= with restricted permissions |
| Docker / Compose | environment: or env_file: |
| Kubernetes | A ConfigMap for ordinary settings and a Secret for passwords and keys |
Value formats
Section titled “Value formats”| Type | Format | Examples |
|---|---|---|
| Duration | A number followed by a unit: s, m, h. Units can be combined |
30s, 15m, 12h, 1h30m |
| List | Comma-separated, no spaces | broker1:9092,broker2:9092 |
| Boolean | true or false |
true |
| Size | A whole number of bytes | 1048576 (1 MiB) |
An invalid value stops SpeculaPro at start-up with a config: error naming the variable.
General
Section titled “General”| Variable | Default | Meaning |
|---|---|---|
SP_ENV |
development |
The environment name. Anything other than development switches on production safeguards: encryption keys become mandatory, the interactive API docs are switched off by default, and browsers are told to use HTTPS only (HSTS). Use production for live systems and, for example, staging for test systems |
SP_ROLES |
all |
Which roles speculapro run starts, as a list. The --role flag overrides it. Available: api, decision, ingest, persist, stream, worker; all starts all six. See Running roles |
SP_METRICS_ADDR |
:9090 |
Address of the separate Prometheus metrics listener, which serves /metrics, plus /healthz and /readyz probes that only report that the process is up (for roles with no HTTP API). off (or an empty value) switches it off. Every process opens it, so two processes on one machine need different ports. If the port is taken, the process stops with metrics listener … instead of running unobserved. Keep it on an internal network |
Deployment and licensing
Section titled “Deployment and licensing”| Variable | Default | Meaning |
|---|---|---|
SP_DEPLOYMENT |
selfhost |
selfhost for an installation you run, where licensing applies; saas for workspaces SpeculaPro operates, where licensing is skipped entirely. Anything else stops SpeculaPro at start-up with config: SP_DEPLOYMENT must be selfhost or saas |
SP_LICENSE_FILE |
(none) | Path to a licence file, for example one mounted from a Kubernetes Secret. Licences installed under Settings → License or with speculapro license install count too; the valid one that runs longest applies. A file that can’t be read is skipped with a warning |
SP_LICENSE_CHECKIN_URL |
(none) | Optional. When set, the worker posts a signed usage report here once a day. Must be https:// (except with SP_ENV=development). Empty means SpeculaPro never contacts SpeculaPro. The licence never depends on it |
Licensing never stops ingest, monitoring, real-time decisions, alerts, cases, webhooks or notifications. See Licensing.
Logging
Section titled “Logging”| Variable | Default | Meaning |
|---|---|---|
SP_LOG_LEVEL |
info |
Minimum level written: debug, info, warn or error. An unrecognised value falls back to info |
SP_LOG_FORMAT |
json |
json (one JSON object per line, for log collectors) or text (easier to read in a terminal). Logs go to standard output |
HTTP server
Section titled “HTTP server”| Variable | Default | Meaning |
|---|---|---|
SP_HTTP_ADDR |
:8080 |
Address and port to listen on. :8080 means every network interface, port 8080. Use 127.0.0.1:8080 to accept connections only from the same machine (for example behind a local reverse proxy) |
SP_HTTP_READ_TIMEOUT |
10s |
Maximum time to read a whole request |
SP_HTTP_WRITE_TIMEOUT |
30s |
Maximum time to write a response |
SP_HTTP_SHUTDOWN_TIMEOUT |
20s |
On stop, how long to let in-flight requests finish before closing |
SP_HTTP_MAX_BODY_BYTES |
1048576 |
Largest accepted request body (1 MiB) for every endpoint except the ingest API, which has its own limit (SP_INGEST_MAX_BODY_BYTES). Two screening endpoints differ: a list import always accepts up to 2 MiB, and a signed list bundle follows SP_INGEST_MAX_BODY_BYTES. Larger requests get 413 Request Entity Too Large |
SP_HTTP_TRUST_PROXY |
false |
Use the client address from the X-Forwarded-For header. Only set true when SpeculaPro sits behind a reverse proxy or load balancer that you control and that adds to this header |
SP_HTTP_PROXY_HOPS |
1 |
With SP_HTTP_TRUST_PROXY=true: how many of your own proxies add to X-Forwarded-For on the way in. 1 for a load balancer; 2 for a CDN in front of a load balancer. The client address is the entry this many places from the right. If the header has fewer entries than that, or the entry isn’t an IP address, the address of the machine that connected is used |
SP_HTTP_API_DOCS |
(unset) | Serve the OpenAPI description and interactive docs at /api/openapi.json and /api/docs. When unset: on in development, off everywhere else |
Security
Section titled “Security”| Variable | Default | Meaning |
|---|---|---|
SP_SECURITY_MASTER_KEYS |
(unset) | Encryption keys for personal data, as <version>:<base64 key> entries separated by commas. The first entry is used for new data; the others are kept so older data can still be read. Required unless SP_ENV=development |
SP_SECURITY_BLIND_INDEX_KEY |
(unset) | Key used to make searchable fingerprints of personal data (phone numbers, national ID numbers) without storing them in clear. Base64, 32 bytes. Required unless SP_ENV=development. Never changes |
SP_SECURITY_SESSION_TTL |
12h |
Longest a sign-in lasts, however active the user is |
SP_SECURITY_SESSION_IDLE |
30m |
A user who does nothing for this long is signed out |
SP_SECURITY_COOKIE_SECURE |
true |
Send the sign-in cookie over HTTPS only, under the hardened name __Host-sp_session. Leave true in production. Browsers treat http://localhost as secure, so local development works with it on |
Generate both keys with speculapro keys generate. Read Encryption keys before you store real data.
Database (PostgreSQL)
Section titled “Database (PostgreSQL)”| Variable | Default | Meaning |
|---|---|---|
SP_DB_URL |
postgres://speculapro:speculapro@localhost:5432/speculapro?sslmode=disable |
Connection string for PostgreSQL 18+ |
SP_DB_SYSTEM_URL |
(unset: uses SP_DB_URL) |
Connection string for the system database role. The decision, persist, stream, connector, analytics and worker roles do their cross-workspace work through it, so in production it names speculapro_system, which bypasses row-level security; they always filter by workspace themselves, and refuse to start unless this user is a superuser or has BYPASSRLS. Every process, api and ingest included, also reads the installation’s own tables through it (the licence and its usage figures), which speculapro_app may not read: where the database roles are separate, set it for every process. See Database roles |
SP_DB_MAX_CONNS |
20 |
Maximum connections per connection pool. Every process with SP_DB_SYSTEM_URL set opens a second pool of the same size. Add them up across processes to check you stay below Postgres’s max_connections. SpeculaPro pools its own connections, so a pooler is often unnecessary; which connection may go through PgBouncer differs by role |
SP_DB_APP_ROLE, SP_DB_SYSTEM_ROLE |
speculapro_app, speculapro_system |
The names of the two database roles. Read by speculapro migrate up, which, after the schema, grants these roles the database functions and table rights they need (the list is deploy/sql/grants.sql), so an upgrade never leaves a function the worker or the API may not call. Change them only if you named your roles differently. If the roles do not exist (a development database with one role), the step is skipped and the log says grants: skipped. |
SP_DB_REPLICA_URL |
(unset) | Optional. A read replica for backtests’ long history reads, used by worker processes with SP_BACKTEST_WORKERS above 0. Its user must bypass row-level security like speculapro_system: at start-up the worker checks, and refuses to start with SP_DB_REPLICA_URL: database role cannot bypass row-level security (use the speculapro_system role on the replica). When set, such a worker opens a third pool of up to SP_DB_MAX_CONNS connections. See A read replica for backtests |
The connection string format is postgres://<user>:<password>@<host>:<port>/<database>?<options>. Useful options:
| Option | Use |
|---|---|
sslmode=verify-full |
Encrypt the connection and check the server’s certificate and hostname. Use this in production |
sslrootcert=/path/ca.pem |
The certificate authority to trust for verify-full |
sslmode=disable |
No encryption. Local development only |
Which database user to connect as is explained in PostgreSQL.
Kafka / Redpanda
Section titled “Kafka / Redpanda”| Variable | Default | Meaning |
|---|---|---|
SP_KAFKA_BROKERS |
localhost:19092 |
List of broker addresses. A few are enough; the client discovers the rest of the cluster |
SP_KAFKA_CLIENT_ID |
speculapro |
Name the client reports to the brokers, visible in broker logs and metrics |
SP_KAFKA_TOPIC_PREFIX |
sp. |
Prefix added to every topic name, so txn.ingested becomes sp.txn.ingested. Change it to share one Kafka cluster between several SpeculaPro environments (for example sp-staging.) |
SP_KAFKA_PARTITIONS |
64 |
Partitions per topic, used only when speculapro topics creates a topic. 16 topics are created, so the broker must accept 16 times this number; use 3 to 6 on a laptop or a shared test broker. See How many partitions the broker accepts |
SP_KAFKA_REPLICATION_FACTOR |
-1 |
Copies of each partition, used only when creating topics. -1 means the broker’s default (1 on a single node). Set 3 on a production cluster |
SP_KAFKA_SASL_MECHANISM |
(unset) | Broker authentication: empty (none), PLAIN, SCRAM-SHA-256 or SCRAM-SHA-512 |
SP_KAFKA_USERNAME |
(unset) | SASL username |
SP_KAFKA_PASSWORD |
(unset) | SASL password |
SP_KAFKA_TLS_ENABLED |
false |
Encrypt connections to the brokers |
SP_KAFKA_TLS_CA_FILE |
(unset) | PEM file of the certificate authority to trust. When unset, the operating system’s trusted authorities are used |
SP_KAFKA_TLS_CERT_FILE |
(unset) | Client certificate (PEM), for mutual TLS |
SP_KAFKA_TLS_KEY_FILE |
(unset) | Client private key (PEM), for mutual TLS |
SP_KAFKA_TLS_SERVER_NAME |
(unset) | Hostname expected on the broker’s certificate, when it differs from the address you connect to |
SP_KAFKA_TLS_INSECURE_SKIP_VERIFY |
false |
Skip certificate checks. Never in production: it makes TLS useless against interception |
Ingest API (role ingest)
Section titled “Ingest API (role ingest)”| Variable | Default | Meaning |
|---|---|---|
SP_INGEST_MAX_BATCH |
500 |
Most items accepted in one request. 500 is also the hard ceiling: this setting can only lower it (a higher or zero value is treated as 500). Senders get 413 above a lowered limit, and 422 above 500 whatever the setting |
SP_INGEST_MAX_BODY_BYTES |
8388608 |
Largest accepted request body on the ingest endpoints (8 MiB). Also the limit for a signed list bundle sent to POST /api/v1/screening/bundles; the bundle format itself allows 96 MiB, and the command line has no such limit |
SP_INGEST_FX_MAX_AGE |
168h |
How old the newest exchange rate may be (7 days). A foreign-currency transaction is rejected if there is no rate for its currency, or the newest one is older than this. Load rates daily; see Currencies and FX |
Persister (role persist)
Section titled “Persister (role persist)”| Variable | Default | Meaning |
|---|---|---|
SP_PERSIST_GROUP |
speculapro-persist |
Kafka consumer group name. Every persist process of one installation must use the same group, so they share the work instead of each writing everything |
SP_PERSIST_BATCH_MAX |
5000 |
Most records written to Postgres in one batch |
SP_PERSIST_WORKERS |
4 |
Parallel writers in this process. The total across all persist processes should not exceed the topic’s partition count (64 by default); extra workers sit idle |
See Persister.
Stream evaluator (role stream)
Section titled “Stream evaluator (role stream)”| Variable | Default | Meaning |
|---|---|---|
SP_STREAM_GROUP |
speculapro-stream |
Kafka consumer group name. Every stream process of one installation must use the same group, and it must differ from SP_PERSIST_GROUP: the two roles each read every transaction |
SP_STREAM_BATCH_MAX |
2000 |
Most transactions evaluated in one batch |
SP_STREAM_WORKERS |
4 |
Parallel evaluators in this process. The total across all stream processes should not exceed the topic’s partition count (64 by default); extra evaluators sit idle |
SP_STREAM_REPLAY_TTL |
30m |
How long the evaluator recognises a transaction it has already counted, so a re-delivered batch isn’t counted twice. Make it longer than your longest expected stream outage. Each transaction holds Redis memory for this long, so memory grows with throughput × this value. At least 1m; a smaller value stops SpeculaPro from starting. See Replays and retries |
Rule changes, lists and suppressions are re-read every 15 seconds; that interval is fixed. See Stream evaluator.
Real-time decisions (role decision)
Section titled “Real-time decisions (role decision)”The decision budget (how long a decision may take) and the failure policy (fail-open or fail-closed) are not set here: each workspace sets its own under Settings → Workspace. See Workspace settings. These settings tune the decision process itself.
| Variable | Default | Meaning |
|---|---|---|
SP_DECISION_STATE_TTL |
10s |
How often each workspace’s rules, lists and decision policy are re-read. A rule activated, a list entry added or a policy saved reaches live decisions within this time |
SP_DECISION_PROFILE_TTL |
30s |
How stale a cached customer, account or counterparty profile may be. A risk designation or a customer update can take this long to reach live decisions |
SP_DECISION_PROFILE_CACHE_SIZE |
500000 |
Most profiles held in each process’s memory. Size it to your active customers and accounts; a profile not in the cache is read from Postgres, which is slower |
SP_DECISION_IDEMPOTENCY_TTL |
72h |
How long a retry with the same external_id gets the original answer back. At least 1h; a smaller value stops SpeculaPro from starting with config: SP_DECISION_IDEMPOTENCY_TTL must be at least 1h. Each decision holds Redis memory for this long |
SP_DECISION_MAX_INFLIGHT |
4096 |
Most decisions this process works on at once. Beyond it, a new request is answered at once by the workspace’s failure policy, marked degraded with the cause overload, instead of waiting in a queue. This stops a slow dependency from building a backlog that would make every later decision late too |
How the role works and how to scale it: Running roles and Redis / Valkey.
Webhooks
Section titled “Webhooks”Webhooks are registered through the api role and delivered by the worker role. Set SP_WEBHOOKS_ALLOW_PRIVATE and SP_WEBHOOKS_ALLOW_HTTP the same way on both.
| Variable | Default | Meaning |
|---|---|---|
SP_WEBHOOKS_ALLOW_PRIVATE |
false |
Allow endpoints on private or internal addresses, such as 10.0.0.5, 192.168.1.20, localhost or *.internal. Off, they are refused when registered and again at every connection. Turn it on only for a self-hosted installation whose receivers sit on your own network. Even when on, link-local and cloud metadata addresses (169.254.0.0/16, fe80::/10, 169.254.169.254 and similar) and 0.0.0.0/8 stay refused, and carrier-grade NAT (100.64.0.0/10) isn’t opened: “private” means RFC 1918, IPv6 unique-local and loopback only. See the address check |
SP_WEBHOOKS_ALLOW_HTTP |
false |
Allow plain http:// endpoints. Development only: with any SP_ENV other than development, SpeculaPro refuses to start with config: SP_WEBHOOKS_ALLOW_HTTP is for development only; webhooks must use https |
SP_WEBHOOKS_TIMEOUT |
10s |
How long one delivery may take, from connecting to the receiver’s answer. Slower answers count as failures. Between 1s and 1m; anything else stops SpeculaPro at start-up with config: SP_WEBHOOKS_TIMEOUT must be between 1s and 1m |
SP_WEBHOOKS_CONCURRENCY |
16 |
Deliveries this worker process makes at the same time. Between 1 and 256; anything else stops SpeculaPro at start-up with config: SP_WEBHOOKS_CONCURRENCY must be between 1 and 256 |
SP_WEBHOOKS_DISABLE_AFTER |
72h |
An endpoint that has failed for this long without a single success is switched off, and everyone with webhooks.manage is told. See When an endpoint is switched off |
SP_WEBHOOKS_RETENTION |
720h |
How long the delivery history is kept (30 days). Older finished deliveries are deleted by the worker’s maintenance run. Events still waiting for an endpoint that has been switched off for this whole period are given up and marked failed |
Single sign-on
Section titled “Single sign-on”Single sign-on is set up per workspace in Settings → Single
sign-on. These settings, read by the api role, apply to the whole installation.
| Variable | Default | Meaning |
|---|---|---|
SP_SSO_PUBLIC_URL |
SP_NOTIFY_PUBLIC_URL |
The address browsers and identity providers use to reach SpeculaPro, for example https://speculapro.yourbank.com.gh. The OIDC redirect URI, the SAML entity ID, ACS URL and single logout URL, the SCIM base URL (<address>/scim/v2) and the post-logout address are built from it, so it must be exactly what people type. If unset, SP_NOTIFY_PUBLIC_URL is used, which itself defaults to http://localhost:8080. Outside SP_ENV=development, single sign-on can’t be turned on while it is local (localhost, a .localhost name or a loopback address) |
SP_SSO_VERIFY_DOMAINS |
true |
A claimed email domain counts only once a DNS TXT record proves the workspace controls it. Keep true for SaaS or any installation shared by several institutions. A self-hosted installation for one institution, whose administrators are trusted, can set false: domains are then verified as soon as they are added |
SP_SSO_ALLOW_PRIVATE |
false |
Let issuer, JWKS, token, userinfo and SAML metadata addresses resolve to private or internal addresses. Turn it on only for a self-hosted installation whose identity provider (Keycloak, ADFS) sits on your own network. Off, such addresses are refused at every connection. Even when on, link-local and cloud metadata addresses (169.254.0.0/16, fe80::/10, 169.254.169.254 and similar) and 0.0.0.0/8 stay refused, and carrier-grade NAT (100.64.0.0/10) isn’t opened: “private” means RFC 1918, IPv6 unique-local and loopback only. See the address check |
SP_SSO_ALLOW_HTTP |
false |
Allow plain http:// issuer and metadata URLs. Development only: with any SP_ENV other than development, SpeculaPro refuses to start with config: SP_SSO_ALLOW_HTTP is for development only; identity providers must use https |
SSO keeps its short-lived sign-in state in Redis, so SSO sign-in needs Redis. While Redis is down, break-glass administrators can still sign in with their password.
Worker (role worker)
Section titled “Worker (role worker)”| Variable | Default | Meaning |
|---|---|---|
SP_WORKER_INTERVAL |
6h |
How often maintenance runs. It also runs once at start-up |
SP_WORKER_INGEST_KEY_RETENTION |
2160h |
How long the record of each external_id you sent is kept (90 days). After that, resending an old external_id is treated as new, not as a duplicate |
See Worker.
Backtests (role worker)
Section titled “Backtests (role worker)”Backtests are run by worker processes. See
Worker: backtests.
| Variable | Default | Meaning |
|---|---|---|
SP_BACKTEST_WORKERS |
2 |
Backtests one worker process runs at once. 0 turns them off in that process, for example on a worker kept for notifications |
SP_BACKTEST_GLOBAL_LIMIT |
4 |
Backtests running at once across the whole installation. Set the same value on every worker |
SP_BACKTEST_TENANT_LIMIT |
1 |
Backtests running at once per workspace. Set the same value on every worker |
SP_BACKTEST_TIMEOUT |
30m |
A run taking longer is stopped and marked failed |
SP_BACKTEST_MAX_MEMORY_MB |
1024 |
Feature memory one run may hold. A run that needs more fails with a message saying so |
SP_BACKTEST_MAX_EVENTS |
30000000 |
Transactions one run may replay, warm-up included. A larger period is refused before it starts. For a scheduled rule’s backtest, the transaction rows its simulated runs read in all, counting a transaction once for each run whose window holds it |
SP_BACKTEST_PAGE_SIZE |
5000 |
Rows per history read |
SpeculaPro refuses to start if SP_BACKTEST_WORKERS is negative, either limit is below 1,
SP_BACKTEST_PAGE_SIZE is below 100 or SP_BACKTEST_MAX_MEMORY_MB is below 64, with:
config: SP_BACKTEST_* limits are out of range (workers >= 0, limits >= 1, page size >= 100, memory >= 64 MB)Risk models (role worker)
Section titled “Risk models (role worker)”Risk models are trained, and their drift measured, by worker
processes. Scoring in the decision and stream roles needs no setting: a workspace without a
shadow or live model isn’t scored at all. See
Risk models for operators.
| Variable | Default | Meaning |
|---|---|---|
SP_MODELS_WORKERS |
1 |
Trainings one worker process runs at once. 0 turns training and drift measurement off in that process |
SP_MODELS_MIN_ROWS |
5000 |
The fewest transactions a training window may hold; a training on less is refused. Set the same value on the api role, which checks it before queuing |
SP_MODELS_TIMEOUT |
1h |
A training taking longer is stopped and marked failed |
SP_MODELS_MAX_MEMORY_MB |
1024 |
Feature memory one training may hold. A training that needs more fails with a message saying so |
SP_MODELS_MAX_EVENTS |
30000000 |
Transactions one training may replay, the history before the window included |
SP_MODELS_DRIFT_DAYS |
7 |
The last so many days are compared with the training data. Set the same value on the api role, which shows it |
SP_MODELS_DRIFT_INTERVAL |
1h |
How often drift is measured |
At most two trainings run at once across the installation, and one per workspace; neither is a
setting. Keep the worker’s memory limit above
SP_BACKTEST_WORKERS × SP_BACKTEST_MAX_MEMORY_MB + SP_MODELS_WORKERS × SP_MODELS_MAX_MEMORY_MB.
SpeculaPro refuses to start if SP_MODELS_WORKERS is negative, SP_MODELS_MIN_ROWS is below
100, SP_MODELS_MAX_MEMORY_MB is below 64, SP_MODELS_MAX_EVENTS is below 1,000,
SP_MODELS_DRIFT_DAYS is outside 1 to 90, or SP_MODELS_DRIFT_INTERVAL or SP_MODELS_TIMEOUT
is under a minute, with:
config: SP_MODELS_* settings are out of range (workers >= 0, min rows >= 100, memory >= 64 MB, max events >= 1000, drift days 1 to 90, drift interval and timeout >= 1m)AI assistant
Section titled “AI assistant”The AI assistant advises analysts on open alerts and never decides. It
is off for every workspace until that workspace switches it on under
Settings → AI assistant. These settings decide what an installation
offers. The api role makes the calls people ask for; the worker role makes the automatic ones.
| Variable | Default | What it does |
|---|---|---|
SP_AI_API_KEY |
(empty) | The installation’s key with the model’s provider (SP_AI_PROVIDER). With it, workspaces can choose Provided with your subscription: their calls are made and counted on this key. Empty: a workspace can only use a key of its own. Treat it as a secret |
SP_AI_PROVIDER |
anthropic |
Whose API the installation’s key is for: anthropic (Claude), openai, google (Gemini), mistral, or custom for any other API that speaks the OpenAI chat format, such as a model you host yourself. Any other value stops start-up. It governs the installation’s key only: a workspace with its own key chooses its own provider in its settings |
SP_AI_BASE_URL |
(empty) | With anthropic: send Claude calls through your own gateway instead of the public API. With openai, google or mistral: use this address instead of the provider’s own. With custom: the address of the API, normally ending in /v1; required when a key is set. Must be an https address outside SP_ENV=development, except with custom, where plain http is accepted for a model on your own network. It isn’t checked against private addresses: it is yours. See the note below on workspaces’ own keys |
SP_AI_MODEL |
claude-sonnet-5-5 |
The model a workspace gets on the installation’s key when it hasn’t chosen one. With anthropic: claude-sonnet-5-5, claude-haiku-5-5 or claude-opus-5-5; any other value stops start-up. With another provider and a key set: that provider’s id for a model, which must not be empty or start with claude-. Workspaces on the installation’s key can’t choose another model then |
SP_AI_MONTHLY_CALLS |
0 |
The default for how many calls a workspace may make on the installation’s key in a calendar month (UTC). 0 is no limit. On a hosted installation a workspace’s plan can set its own number, which takes over from this one, or leave the installation’s key out; without the provider console this setting is the only allowance. A call is counted before it is made, one at a time for each workspace, so the limit can’t be overrun, and a call that fails at the provider still counts (it may be billed). Past the limit, a request is refused with 429 and automatic reviews stop for that workspace until the next month. A workspace’s own key is never limited |
Setting none of these sends nothing anywhere: no call is made for a workspace that hasn’t switched
the assistant on. With the assistant on, the pods that make the calls need outbound https access to
the API or to your gateway, and to the address of every provider a workspace may choose with its
own key.
Start-up refuses a configuration that can’t work:
config: SP_AI_PROVIDER must be anthropic, openai, google, mistral or customconfig: SP_AI_MODEL must name a model of SP_AI_PROVIDER=openaiconfig: SP_AI_PROVIDER=custom needs SP_AI_BASE_URL, the address of the OpenAI-compatible API (ending in /v1)config: SP_AI_BASE_URL must be an https addressWorkspaces’ own keys. A workspace that brings its own key chooses a provider under
Settings → AI assistant, whatever SP_AI_PROVIDER says:
| The workspace’s provider | Where its calls go |
|---|---|
| Anthropic (Claude) | With SP_AI_PROVIDER=anthropic and SP_AI_BASE_URL set: that gateway. Whoever runs it handles the workspace’s key, and the workspace’s settings tab says calls to Claude go through a gateway the operator runs. Otherwise straight to Anthropic’s API |
| OpenAI, Google (Gemini), Mistral | Straight to that provider’s address. Never through SP_AI_BASE_URL |
| Another OpenAI-compatible API | The address the workspace gave. It must be a public https address unless you set SP_CONNECTOR_ALLOW_PRIVATE=true (private and internal addresses) or SP_CONNECTOR_ALLOW_HTTP=true (plain http), the same two switches that govern connectors. Link-local and cloud metadata addresses are refused either way, at every connection, and redirects are not followed |
The OpenAI-compatible path has been verified against local stand-in servers, not against any real provider, and support for structured JSON output differs between models. Try the model you configure before you offer it.
Asking for more calls. A workspace’s administrators can ask for more calls only where the
installation runs the provider console and has a key. Without the console,
SP_AI_MONTHLY_CALLS is the only allowance, and the settings tab tells them to ask whoever runs
the installation.
Analytics store (ClickHouse)
Section titled “Analytics store (ClickHouse)”The optional analytics store. Leave SP_CLICKHOUSE_URL empty and
none of these settings does anything: everything reads PostgreSQL. Set the connection settings on
every process: api and worker read the store, and the analytics role fills it.
Connection
| Variable | Default | Meaning |
|---|---|---|
SP_CLICKHOUSE_URL |
empty | ClickHouse’s native-protocol address: clickhouse://host:9000, or port 9440 with TLS. Setting it switches the analytics store on. No user or password in it |
SP_CLICKHOUSE_USER |
default |
The ClickHouse user |
SP_CLICKHOUSE_PASSWORD |
empty | Its password. A secret |
SP_CLICKHOUSE_DATABASE |
speculapro |
The database. speculapro migrate up creates it. Letters, digits and underscores only |
SP_CLICKHOUSE_TLS_ENABLED |
false |
Connect with TLS |
SP_CLICKHOUSE_TLS_CA_FILE |
empty | The CA that signed ClickHouse’s certificate. Empty: the system’s trusted roots |
SP_CLICKHOUSE_TLS_CERT_FILE, SP_CLICKHOUSE_TLS_KEY_FILE |
empty | A client certificate and key, for mutual TLS |
SP_CLICKHOUSE_TLS_SERVER_NAME |
empty | The name to check the certificate against, when it differs from the host in the URL |
SP_CLICKHOUSE_TLS_INSECURE_SKIP_VERIFY |
false |
Skip certificate checks. For testing only |
SP_CLICKHOUSE_MAX_CONNS |
10 |
Connections each process may open |
SP_CLICKHOUSE_DIAL_TIMEOUT |
5s |
How long connecting may take |
SP_CLICKHOUSE_QUERY_TIMEOUT |
30s |
How long an Analytics page or scheduled-rule read may run. Backtests read for as long as SP_BACKTEST_TIMEOUT allows |
SP_CLICKHOUSE_READER_ROLE |
empty | A ClickHouse role that speculapro migrate up creates, grants SELECT, INSERT and ALTER DELETE on the database, and puts row policies on, as a second layer of workspace isolation. Empty: no row policies |
Who reads it (roles api and worker)
| Variable | Default | Meaning |
|---|---|---|
SP_CLICKHOUSE_BACKTEST_THRESHOLD |
720h |
A backtest whose history, warm-up included, spans more than this (30 days) reads its transactions from ClickHouse, when the workspace’s copy is complete and the writer has caught up. Set the same value on every worker |
SP_CLICKHOUSE_SCENARIO_THRESHOLD |
720h |
A scheduled rule with a ClickHouse version (I2, I3) reads it when its window is at least this long, the workspace’s copy is complete from the window’s start and the writer has caught up |
The analytics role
| Variable | Default | Meaning |
|---|---|---|
SP_CLICKHOUSE_GROUP |
speculapro-analytics |
The role’s Kafka consumer group. Renaming it makes the role re-read everything Kafka retains (safely, but slowly) |
SP_CLICKHOUSE_WORKERS |
2 |
Kafka consumers per analytics process. The total across all analytics processes shouldn’t exceed the partition count of txn.ingested |
SP_CLICKHOUSE_BATCH_MAX |
50000 |
The most rows in one insert. ClickHouse prefers few large inserts |
SP_CLICKHOUSE_FLUSH_INTERVAL |
2s |
How long a batch that isn’t full waits before it is written. It is the store’s normal delay behind live |
SP_CLICKHOUSE_SYNC_INTERVAL |
30s |
How often rule hits, alerts and cases are copied from PostgreSQL |
SP_CLICKHOUSE_SYNC_OVERLAP |
5m |
How far before its last copy each pass re-reads, so rows committed late aren’t missed |
With SP_CLICKHOUSE_URL set, SpeculaPro refuses to start on a bad value:
config: SP_CLICKHOUSE_URL must be a native-protocol address such as clickhouse://clickhouse:9000config: put ClickHouse credentials in SP_CLICKHOUSE_USER and SP_CLICKHOUSE_PASSWORD, not in the URLconfig: SP_CLICKHOUSE_DATABASE must be a plain identifier (letters, digits, underscore)config: SP_CLICKHOUSE_READER_ROLE must be a plain identifierconfig: SP_CLICKHOUSE_BACKTEST_THRESHOLD is 0s; it must be between 1h0m0s and 87600h0m0sThe last form names the variable, the value it read and the range it accepts. Every number is checked:
| Variable | Accepted range |
|---|---|
SP_CLICKHOUSE_MAX_CONNS |
1 to 1000 |
SP_CLICKHOUSE_BATCH_MAX |
100 to 1,000,000 |
SP_CLICKHOUSE_WORKERS |
1 to 64 |
SP_CLICKHOUSE_DIAL_TIMEOUT |
100ms to 2m |
SP_CLICKHOUSE_QUERY_TIMEOUT |
1s to 1h |
SP_CLICKHOUSE_FLUSH_INTERVAL |
10ms to 5m |
SP_CLICKHOUSE_SYNC_INTERVAL |
1s to 1h |
SP_CLICKHOUSE_SYNC_OVERLAP |
1s to 24h |
SP_CLICKHOUSE_BACKTEST_THRESHOLD, SP_CLICKHOUSE_SCENARIO_THRESHOLD |
1h to 87600h (ten years) |
The thresholds can’t be zero or negative: that would send every backtest and scheduled rule to
ClickHouse, however short, where the newest transactions arrive a moment later than in
PostgreSQL. SP_CLICKHOUSE_GROUP must be a Kafka group name and SP_CLICKHOUSE_USER can’t be
empty.
Screening (role worker)
Section titled “Screening (role worker)”Sanctions and PEP screening: the worker fetches the lists and screens; api
processes answer from what is stored. Set these the same on every process. See
Screening lists for operators.
| Variable | Default | Meaning |
|---|---|---|
SP_SCREENING_ENABLED |
true |
false: the worker does no screening work (no list fetches, no screening, no re-screens), and decision processes screen no counterparty inline. The settings page and API still answer from stored data |
SP_SCREENING_AIR_GAPPED |
false |
true: never fetch a list from its publisher. Lists come from signed list bundles and imports only |
SP_SCREENING_ALLOW_PRIVATE |
false |
true: list fetches and screening providers may reach private network addresses (a yente server or list mirror on your own network), and a provider address or a list address in SP_SCREENING_SOURCE_URLS may be plain http. Link-local and cloud metadata addresses stay refused. Self-hosted only: refused when SP_DEPLOYMENT=saas (SpeculaPro doesn’t start), like SP_CONNECTOR_ALLOW_PRIVATE. See the caution |
SP_SCREENING_FETCH_TIMEOUT |
5m |
The longest one list download may take. At least 1s |
SP_SCREENING_INTERVAL |
30s |
Time between the worker’s screening passes (list fetches that are due, list changes, new and changed customers and counterparties, provider retries, queued re-screens). At least 1s |
SP_SCREENING_BATCH_SIZE |
1000 |
Customers or counterparties screened per database transaction. From 1 to 10000 |
SP_SCREENING_SOURCE_URLS |
(empty) | Your own download address for a built-in list, as space-separated key=url pairs, for example ke-frc=https://www.frc.go.ke/wp-content/uploads/2026/09/Domestic-List_Kenya.xlsx. Keys: un-sc, us-ofac-sdn, us-ofac-cons, eu-fsf, gb-uksl, za-fic, ca-sema, au-dfat, ke-frc. https only (http only with SP_SCREENING_ALLOW_PRIVATE=true); the address goes through the same destination checks as any fetch. No user name, password or #fragment in an address. It wins over an address stored with speculapro screening source set-url <key> <url>, which wins over the built-in address; start-up overwrites neither. See When a publisher moves a list |
SP_SCREENING_MAX_REMOVAL_PERCENT |
20 |
A fetched list or signed bundle that would remove more than this percentage of the current version’s entries is refused, and the current version stays, until someone accepts it: the operator with speculapro screening source fetch <key> --accept-removal or screening import-bundle <file> --accept-removal, or a person with screening.accept_removal with accept_removal=true on the bundle import API. Helm: screening.maxRemovalPercent. From 1 to 100; 100 switches the check off. See When a list loses many entries |
SP_SCREENING_MAX_REMOVAL_MIN_ENTRIES |
50 |
Lists whose current version has fewer entries than this are not checked. At least 1. Helm: screening.maxRemovalMinEntries |
SpeculaPro checks these when it starts, and refuses to start when one is wrong:
| Refused | Message |
|---|---|
SP_SCREENING_ALLOW_PRIVATE=true with SP_DEPLOYMENT=saas |
config: SP_SCREENING_ALLOW_PRIVATE can't be set when SP_DEPLOYMENT=saas |
SP_SCREENING_INTERVAL or SP_SCREENING_FETCH_TIMEOUT below 1s |
config: SP_SCREENING_INTERVAL and SP_SCREENING_FETCH_TIMEOUT must be at least 1s |
SP_SCREENING_BATCH_SIZE below 1 or above 10000 |
config: SP_SCREENING_BATCH_SIZE must be between 1 and 10000, got … |
SP_SCREENING_MAX_REMOVAL_PERCENT below 1 or above 100, or SP_SCREENING_MAX_REMOVAL_MIN_ENTRIES below 1 |
config: SP_SCREENING_MAX_REMOVAL_PERCENT must be between 1 and 100 and SP_SCREENING_MAX_REMOVAL_MIN_ENTRIES at least 1 |
A pair in SP_SCREENING_SOURCE_URLS that is not key=url |
config: SP_SCREENING_SOURCE_URLS: "…" is not key=url (pairs are separated by spaces) |
A key twice in SP_SCREENING_SOURCE_URLS |
config: SP_SCREENING_SOURCE_URLS names … twice |
A key in SP_SCREENING_SOURCE_URLS that is not a fetched list |
config: SP_SCREENING_*: invalid: … is not a fetched list (the fetched lists: …) |
An address in SP_SCREENING_SOURCE_URLS that is not https, is malformed or carries credentials, or points at a private or metadata host |
config: SP_SCREENING_*: invalid: the URL for … must be https, … is not valid, or … : destination not allowed: … |
With the Helm chart these are the screening values; see
Kubernetes with Helm.
The speculapro screening commands (signed bundles, installation lists from a file, list addresses,
installation-wide providers) are described in
Screening lists for operators. They connect with
SP_DB_SYSTEM_URL when it is set; speculapro screening provider set also needs
SP_SECURITY_MASTER_KEYS, to encrypt the provider’s credentials.
Multi-region (cells and the directory)
Section titled “Multi-region (cells and the directory)”Only for a hosted service that runs in more than one region. Leave all of these unset on a single
installation, which is every self-hosted one: nothing changes. See
Running more than one region for what they do, how to generate the
keys and what stops start-up. .env.example lists them all, commented out.
| Setting | Default | Meaning |
|---|---|---|
SP_CELL_REGION |
(empty) | This installation’s region key: 2 to 11 characters, a lowercase letter first, then lowercase letters, digits or dashes. Never change it once workspaces exist |
SP_CELL_NAME |
the region key | The region’s name as people see it |
SP_CELL_PUBLIC_URL |
(empty) | Where this region is reached. Required with SP_DIRECTORY_URL |
SP_DIRECTORY_URL |
(empty) | The directory’s address (https). Needs SP_CELL_REGION |
SP_DIRECTORY_KEY |
(empty) | Secret. This cell’s signing key, <version>:<base64 of 32 bytes>, from speculapro directory keygen <region>. Required with SP_DIRECTORY_URL |
SP_DIRECTORY_PUBLIC_URL |
(empty) | The global sign-in address, linked from this region’s sign-in page |
SP_DIRECTORY_ALLOW_PRIVATE |
false |
The directory is on your private network |
SP_DIRECTORY_TIMEOUT |
10s |
Per call to the directory; 1s to 1m |
SP_DIRECTORY_ALLOW_HTTP |
false |
Development only |
Read only by the directory itself (speculapro directory run):
| Setting | Default | Meaning |
|---|---|---|
SP_DIRECTORY_DB_URL |
(empty) | Secret. The directory’s own database, never a cell’s |
SP_DIRECTORY_CELL_KEYS |
(empty) | Secret. The cells’ keys, gh:1:<base64>,eu:1:<base64> |
SP_DIRECTORY_ADDR |
:8085 |
Listen address |
SP_DIRECTORY_RESOLVE_PER_MINUTE |
20 |
Workspace look-ups one client address may make a minute |
Provider console (hosted service only)
Section titled “Provider console (hosted service only)”See Setting up the provider console.
| Setting | Default | Meaning |
|---|---|---|
SP_PROVIDER_CONSOLE_ENABLED |
false |
Serves the provider console from api processes. Only with SP_DEPLOYMENT=saas; otherwise SpeculaPro doesn’t start. Needs SP_PROVIDER_ADDR, SP_DB_SYSTEM_URL and SP_SECURITY_COOKIE_SECURE=true |
SP_PROVIDER_ADDR |
none | The console’s own listener, for example :8090. Required when the console is on; must differ from SP_HTTP_ADDR and SP_METRICS_ADDR |
SP_PROVIDER_ALLOWED_CIDRS |
empty | Comma-separated networks or single addresses staff may come from. Everything else is answered 403. Empty: whoever the network lets through |
SP_PROVIDER_SESSION_IDLE |
30m |
Idle timeout of a staff session, 1m to 1h |
SP_PROVIDER_SESSION_TTL |
8h |
Longest a staff session lasts, from the idle timeout up to 12h |
SP_PROVIDER_PURGE_ENABLED |
false |
Lets the worker delete a workspace whose scheduled deletion is due. Only with SP_DEPLOYMENT=saas. Set it for the worker and api processes alike. See Scheduled deletion |
SP_PROVIDER_DEV_NO_TOTP |
false |
Development only: staff sign in without a second factor. Refused outside SP_ENV=development |
Connectors
Section titled “Connectors”The connector role reads institution Kafka topics, SFTP / S3 file drops and change data capture
sources (Debezium topics and Postgres replication) set up under Settings → Data sources. See Connector. Set
SP_CONNECTOR_ALLOW_PRIVATE, SP_CONNECTOR_ALLOW_HTTP, SP_CONNECTOR_KAFKA_TOPIC_NAMESPACE and
SP_CONNECTOR_KAFKA_SHARED_TOPICS the same on the api role, which checks data sources against
them when they are saved. Test connection runs on a connector (in the same process in all,
otherwise queued for one), so only connectors need outbound access.
| Variable | Default | Meaning |
|---|---|---|
SP_CONNECTOR_ALLOW_PRIVATE |
false |
Allow SFTP servers and object stores on private or internal addresses. Turn it on for a self-hosted installation whose servers sit on your network; never on one shared by several institutions: with SP_DEPLOYMENT=saas, SpeculaPro refuses to start with it (config: SP_CONNECTOR_ALLOW_PRIVATE can't be set when SP_DEPLOYMENT=saas). Off, such hosts are refused when a data source is saved and again at every connection. Even when on, link-local and cloud metadata addresses (169.254.0.0/16, fe80::/10, 169.254.169.254 and similar) and 0.0.0.0/8 stay refused, and carrier-grade NAT (100.64.0.0/10) isn’t opened: “private” means RFC 1918, IPv6 unique-local and loopback only. See the address check |
SP_CONNECTOR_ALLOW_HTTP |
false |
Allow plain-http S3 endpoints, such as an on-premises MinIO without TLS. Credentials then travel in the clear: development or a private network only |
SP_CONNECTOR_INTERVAL |
30s |
How often due file drops are looked for (at least 1s). Run now takes effect within this |
SP_CONNECTOR_CONCURRENCY |
2 |
File drops this process runs at once, 1 to 64 |
SP_CONNECTOR_FILE_WORKERS |
4 |
Batches of one file loaded in parallel, 1 to 64 |
SP_CONNECTOR_CONNECT_TIMEOUT |
15s |
Connecting, including the SSH handshake (at least 1s) |
SP_CONNECTOR_READ_TIMEOUT |
60s |
A transfer making no progress for this long fails (at least 1s) |
SP_CONNECTOR_MAX_FILE_BYTES |
2147483648 |
The largest file, in bytes (2 GiB; at least 1 MiB). Also caps a .gz file’s decompressed content. The Helm chart sets 900 MiB |
SP_CONNECTOR_MAX_FILES_PER_RUN |
100 |
Files one run of a data source loads; the rest wait for the next run |
SP_CONNECTOR_SPOOL_DIR |
Empty: the OS temporary directory | Where files are staged while they load. Needs room for the largest file times SP_CONNECTOR_CONCURRENCY |
SP_CONNECTOR_MAX_STORED_RESULTS_BYTES |
33554432 |
Results files up to this size (compressed; 32 MiB) are kept for download in the app. Larger ones are only written back to the source |
SP_CONNECTOR_HISTORY_RETENTION |
2160h |
How long file history and stored results are kept (90 days). After that, a file left in place is no longer recognised and is loaded again |
SP_CONNECTOR_KAFKA_BROKERS |
Empty | The cluster institutions publish to, comma-separated. Empty: SpeculaPro’s own cluster and its SP_KAFKA_* settings |
SP_CONNECTOR_KAFKA_SASL_MECHANISM, _USERNAME, _PASSWORD |
Empty | Credentials for that cluster: PLAIN, SCRAM-SHA-256 or SCRAM-SHA-512 |
SP_CONNECTOR_KAFKA_TLS_ENABLED |
false |
TLS to that cluster, with _TLS_CA_FILE, _TLS_CERT_FILE, _TLS_KEY_FILE and _TLS_SERVER_NAME as for Kafka |
SP_CONNECTOR_KAFKA_GROUP |
speculapro-connector |
The connector’s consumer group |
SP_CONNECTOR_KAFKA_TOPIC_NAMESPACE |
Empty | On a cluster shared by several institutions: each workspace may only read topics named <namespace><workspace slug>.<name>. Your broker’s access rules must let each institution write only to its own namespace. Empty: any topic. See Who may read which topic |
SP_CONNECTOR_KAFKA_SHARED_TOPICS |
Empty | Comma-separated topics that several workspaces may read, each record naming its workspace in the sp-workspace header. Only for producers you trust to name any workspace |
SP_CONNECTOR_KAFKA_BATCH_MAX |
2000 |
Records fetched at a time |
SP_CONNECTOR_KAFKA_RELOAD_INTERVAL |
30s |
How often topic mappings are re-read |
SP_CONNECTOR_KAFKA_PAUSE_LAG |
500000 |
Pause reading while the persister or stream evaluator is this many records behind on SpeculaPro’s transaction topic; resume below half. 0 never pauses |
SP_CONNECTOR_CDC_ALLOW_PLAINTEXT |
false |
Allow Postgres replication sources that don’t verify the server: ssl_mode require (TLS, any certificate accepted) and disable (no TLS). Without it only verify-full and verify-ca are accepted. The replication user’s password and every row could then reach an impostor or travel in the clear: a private development network only, and refused when SP_DEPLOYMENT=saas. Set it the same on the api role, which checks sources when they are saved. See Postgres replication for DBAs: TLS |
SP_CONNECTOR_CDC_BATCH_MAX |
2000 |
Change data capture: changes applied at once. 1 to 50,000 |
SP_CONNECTOR_CDC_FLUSH_INTERVAL |
500ms |
The longest a change waits before it is applied. 10ms to 1m |
SP_CONNECTOR_CDC_STATUS_INTERVAL |
10s |
How often the connector confirms its position to a Postgres source. 1s to 1m |
SP_CONNECTOR_CDC_SNAPSHOT_BATCH |
5000 |
Rows fetched at once during a Postgres source’s first copy. 1 to 100,000 |
SP_CONNECTOR_CDC_LAG_INTERVAL |
30s |
How often a Postgres replication slot’s lag is measured. 5s to 1h |
SP_CONNECTOR_CDC_SLOT_LAG_ALERT_BYTES |
1073741824 |
A replication slot further behind than this (1 GiB) raises the alert: the source shows Falling behind, data-source managers are notified, and speculapro_cdc_slot_behind is 1. At least 1 MiB |
SP_CONNECTOR_CDC_APPLIED_RETENTION_DAYS |
30 |
How long the ledger of applied changes is kept, which recognises a change read twice. A change replayed after that is still caught by the usual duplicate protection. 1 to 3650. SpeculaPro refuses to start on a value outside any of these ranges |
SpeculaPro refuses to start if SP_CONNECTOR_FILE_WORKERS or SP_CONNECTOR_CONCURRENCY is outside
1 to 64, SP_CONNECTOR_MAX_FILE_BYTES is below 1 MiB, or the interval or a timeout is below one
second, for example with config: SP_CONNECTOR_FILE_WORKERS must be between 1 and 64, got 0.
Notifications
Section titled “Notifications”Email, text-message and WhatsApp notifications are sent by the worker role (WhatsApp verification codes and test messages by the api role). People choose their own channels in the app (Notifications); these settings decide how messages leave your installation.
| Variable | Default | Meaning |
|---|---|---|
SP_NOTIFY_PUBLIC_URL |
http://localhost:8080 |
The address people use to open SpeculaPro, for example https://speculapro.yourbank.com.gh. Every message links here. Outside SP_ENV=development, when the email provider is smtp or spalce, the SMS provider is spalce or the WhatsApp provider is gowa, it must be set to a real address: SpeculaPro refuses to start if it is empty or points at localhost or 127.0.0.1 |
SP_NOTIFY_EMAIL_PROVIDER |
log |
How email is sent: smtp (your mail server), spalce (the Spalce Notification Service), log (written to the log only, for development) or none (not sent) |
SP_NOTIFY_SMS_PROVIDER |
none |
How text messages are sent: spalce, log (development only) or none. Outside SP_ENV=development, log stops SpeculaPro at start-up |
SP_NOTIFY_WHATSAPP_PROVIDER |
none |
How WhatsApp messages are sent: gowa (your own GOWA server), log (development only) or none (people can’t choose WhatsApp). Outside SP_ENV=development, log stops SpeculaPro at start-up |
SP_NOTIFY_LOG_SHOW_CODES |
false |
Development only. The log provider normally masks phone numbers to their last three digits and writes one-time codes as [code]; true prints codes in full, so you can confirm a WhatsApp number on your own machine. With any SP_ENV other than development, SpeculaPro refuses to start with config: SP_NOTIFY_LOG_SHOW_CODES is for development only. The development stack sets it |
SP_NOTIFY_DEFAULT_COUNTRY_CODE |
233 |
The country calling code added to a local number someone types without one, so 0244 123 456 becomes +233244123456. Digits only, no + |
SP_NOTIFY_FROM_EMAIL |
[email protected] |
Sender address. Use an address on your own domain that your mail server is allowed to send from (SPF/DKIM), or messages land in spam |
SP_NOTIFY_FROM_NAME |
SpeculaPro |
Sender name shown in mail clients |
SP_NOTIFY_EMAIL_COMPANY_NAME |
(empty) | Legal name of the company behind the service, shown in the footer of notification emails. Left out while empty |
SP_NOTIFY_EMAIL_COMPANY_ADDRESS |
(empty) | Its registered or postal address, on one line, shown after the company name. Left out while empty |
SP_NOTIFY_EMAIL_WEBSITE |
speculapro.com |
Website named in the email footer |
SP_NOTIFY_EMAIL_SUPPORT_EMAIL |
[email protected] |
Support address in the email footer (“Need help?”) |
SP_NOTIFY_EMAIL_SUPPORT_PHONE |
(empty) | Support phone in international format, shown after the support address. Left out while empty |
SP_NOTIFY_INTERVAL |
5s |
How often the worker looks for messages to send |
SP_NOTIFY_MAX_ATTEMPTS |
6 |
How many times a message is tried before it is marked failed. Retries wait 30 seconds, then 1, 2, 4 and 8 minutes, doubling up to an hour, so the default gives up after about 15 minutes. If your mail server can be down longer than that, raise it: 10 keeps trying for about 3 hours, 12 for about 5. A logged-out WhatsApp session doesn’t use up these tries: see SP_NOTIFY_WHATSAPP_MAX_WAIT |
Notification emails use one designed layout in English and French: see What an email looks like. With smtp each email is sent with a plain-text part and an HTML part; with spalce the HTML part only. The button and the settings link appear only when SP_NOTIFY_PUBLIC_URL is an http or https address. Developers can write every email in both languages to a folder to look at in a browser: cd backend && SP_EMAIL_SAMPLES=<folder> go test ./internal/notify -run TestEmailSamples.
GOWA (when SP_NOTIFY_WHATSAPP_PROVIDER=gowa; see WhatsApp through GOWA):
| Variable | Default | Meaning |
|---|---|---|
SP_NOTIFY_GOWA_BASE_URL |
(none) | Your GOWA server’s address, including GOWA’s APP_BASE_PATH if you set one, for example http://gowa:3000. Required with gowa. Must be https unless SP_NOTIFY_GOWA_ALLOW_PRIVATE=true. No user name or password in it, and no ?query or #fragment. A change is recorded in the audit log when a worker starts with it |
SP_NOTIFY_GOWA_USERNAME |
(empty) | The user name from GOWA’s APP_BASIC_AUTH. Required outside development: SpeculaPro refuses to start with a GOWA server that has no user name and password unless SP_ENV=development |
SP_NOTIFY_GOWA_PASSWORD |
(empty) | The password from GOWA’s APP_BASIC_AUTH. Keep it in your secret manager. Set both, or (in development only) neither |
SP_NOTIFY_GOWA_DEVICE_ID |
(empty) | Sent as X-Device-Id. Needed only when your GOWA server holds more than one WhatsApp device |
SP_NOTIFY_GOWA_ALLOW_PRIVATE |
false |
true lets SpeculaPro reach GOWA at a private address (RFC 1918 or IPv6 unique-local) or loopback, and over plain http. Link-local and cloud metadata addresses stay refused. Normal when GOWA runs next to SpeculaPro; see the trade-off |
SP_NOTIFY_GOWA_TIMEOUT |
15s |
How long to wait for GOWA to answer one request |
SP_NOTIFY_GOWA_STATUS_INTERVAL |
1m |
How often the worker checks that the WhatsApp session is still connected and logged in. At least 5s |
SP_NOTIFY_WHATSAPP_MAX_WAIT |
24h |
While the WhatsApp session is logged out or its device is missing, messages wait in the outbox, tried again every 30 seconds rising to every 5 minutes, without using up SP_NOTIFY_MAX_ATTEMPTS. A message still waiting this long after it was raised is marked failed. At least 1m |
SMTP (when SP_NOTIFY_EMAIL_PROVIDER=smtp):
| Variable | Default | Meaning |
|---|---|---|
SP_NOTIFY_SMTP_HOST |
(none) | Your mail server. Required with smtp, or SpeculaPro won’t start |
SP_NOTIFY_SMTP_PORT |
587 |
Usually 587 with starttls, 465 with tls |
SP_NOTIFY_SMTP_USERNAME |
(empty) | Leave empty for a relay that doesn’t need a login |
SP_NOTIFY_SMTP_PASSWORD |
(empty) | Keep it in your secret manager |
SP_NOTIFY_SMTP_SECURITY |
starttls |
starttls (upgrade to TLS after connecting; port 587), tls (TLS from the start; port 465) or none (no encryption: only for a relay on the same machine or private network) |
Spalce Notification Service (when either provider is spalce; one account covers email and SMS):
| Variable | Default | Meaning |
|---|---|---|
SP_NOTIFY_SPALCE_BASE_URL |
https://notify.spalce.net |
Service address |
SP_NOTIFY_SPALCE_API_KEY |
(none) | Required with spalce |
SP_NOTIFY_SPALCE_MERCHANT_CODE |
(none) | Required with spalce |
SP_NOTIFY_SPALCE_COUNTRY_CODE |
GH |
Your country, as two letters |
SP_NOTIFY_SPALCE_SMS_SENDER_ID |
SpeculaPro |
The name text messages come from. It must be a sender ID registered with the SMS gateway, or messages are refused |
At start-up SpeculaPro checks these settings and refuses to start with a clear message if a provider name is wrong (config: SP_NOTIFY_EMAIL_PROVIDER must be log, smtp, spalce or none; config: SP_NOTIFY_WHATSAPP_PROVIDER must be log, gowa or none), uses the log provider for SMS or WhatsApp outside development (config: SP_NOTIFY_WHATSAPP_PROVIDER and SP_NOTIFY_SMS_PROVIDER can't be log outside development; choose a provider or none) or a required value is missing (config: SP_NOTIFY_SMTP_HOST is required when SP_NOTIFY_EMAIL_PROVIDER=smtp). The GOWA checks and their messages are listed in WhatsApp through GOWA. How sending works, retries and what you see in the logs: Worker: notifications.
Redis / Valkey
Section titled “Redis / Valkey”| Variable | Default | Meaning |
|---|---|---|
SP_REDIS_ADDRS |
localhost:6379 |
List of addresses. What you list decides the mode (see below) |
SP_REDIS_SENTINEL_MASTER |
(unset) | The name of the primary that the Sentinels watch (often mymaster). Setting it switches on Sentinel mode |
SP_REDIS_USERNAME |
(unset) | Username (Redis/Valkey ACLs) |
SP_REDIS_PASSWORD |
(unset) | Password |
SP_REDIS_DB |
0 |
Database number (standalone and Sentinel only; Cluster always uses 0) |
SP_REDIS_SENTINEL_USERNAME |
(unset) | Username for the Sentinels themselves, if they require authentication |
SP_REDIS_SENTINEL_PASSWORD |
(unset) | Password for the Sentinels themselves |
SP_REDIS_POOL_SIZE |
0 |
Connections per pool. 0 means 10 for each CPU the process can use. A process running the decision role opens a second pool of this size for decisions alone, so that other work in the same process can’t take the connections a live decision is waiting for. Raise it on busy decision nodes. In Cluster mode the size applies to each node |
SP_REDIS_MIN_IDLE_CONNS |
8 |
Connections kept open and ready in each pool, so a burst doesn’t wait for new connections |
SP_REDIS_TLS_ENABLED, SP_REDIS_TLS_CA_FILE, SP_REDIS_TLS_CERT_FILE, SP_REDIS_TLS_KEY_FILE, SP_REDIS_TLS_SERVER_NAME, SP_REDIS_TLS_INSECURE_SKIP_VERIFY |
as for Kafka | TLS settings, with the same meaning as the Kafka ones above |
How the mode is chosen:
| You set | Mode |
|---|---|
One address, no SP_REDIS_SENTINEL_MASTER |
Standalone: one server |
SP_REDIS_SENTINEL_MASTER and the Sentinel addresses (usually port 26379) |
Sentinel: automatic failover between a primary and replicas |
Several addresses, no SP_REDIS_SENTINEL_MASTER |
Cluster: data spread over several primaries |
# SentinelSP_REDIS_ADDRS=s1:26379,s2:26379,s3:26379SP_REDIS_SENTINEL_MASTER=mymaster
# ClusterSP_REDIS_ADDRS=n1:6379,n2:6379,n3:6379See Redis / Valkey for which to choose.
Tracing (OpenTelemetry)
Section titled “Tracing (OpenTelemetry)”Tracing is off unless you set an OTLP endpoint. SpeculaPro then sends traces over OTLP/gRPC to your OpenTelemetry Collector (or any backend that accepts OTLP, such as Grafana Tempo or Jaeger). These are the standard OpenTelemetry variables, without the SP_ prefix, so they work the way your observability team already knows. What a trace contains, and what it doesn’t: Monitoring: tracing.
| Variable | Default | Meaning |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
(unset: tracing off) | Your collector’s OTLP/gRPC address, for example https://otel-collector.internal:4317. Use http:// only for a collector on the same machine or a trusted network |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
(unset) | Same, for traces only. Setting either one switches tracing on |
OTEL_EXPORTER_OTLP_HEADERS |
(unset) | Headers sent to the collector, as key=value pairs separated by commas, for example authorization=Bearer <token>. Treat it as a secret |
OTEL_SERVICE_NAME |
speculapro |
The service name traces are filed under. Set it per role group (for example speculapro-decision) if you want to tell them apart |
OTEL_TRACES_SAMPLER |
parentbased_always_on |
Which requests are traced. The default traces every one |
OTEL_TRACES_SAMPLER_ARG |
(unset) | The sampler’s setting, such as the fraction to keep |
OTEL_RESOURCE_ATTRIBUTES |
(unset) | Extra labels for every trace, for example deployment.environment=production |
Other standard OTEL_EXPORTER_OTLP_* settings, such as OTEL_EXPORTER_OTLP_CERTIFICATE for a private certificate authority, also apply.
If the tracing settings are wrong (for example an unreadable endpoint), SpeculaPro stops at start-up with telemetry: …. When tracing starts, the log says tracing enabled.
Example: production settings
Section titled “Example: production settings”SP_ENV=productionSP_ROLES=allSP_LOG_FORMAT=json
SP_HTTP_ADDR=127.0.0.1:8080SP_HTTP_TRUST_PROXY=true # behind your own reverse proxy
SP_SECURITY_MASTER_KEYS=1:<from keys generate>SP_SECURITY_BLIND_INDEX_KEY=<from keys generate>
SP_DB_URL=postgres://speculapro_app:<password>@pg.internal:5432/speculapro?sslmode=verify-full&sslrootcert=/etc/speculapro/pg-ca.pemSP_DB_SYSTEM_URL=postgres://speculapro_system:<password>@pg.internal:5432/speculapro?sslmode=verify-full&sslrootcert=/etc/speculapro/pg-ca.pemSP_DB_MAX_CONNS=20
SP_KAFKA_BROKERS=kafka1.internal:9092,kafka2.internal:9092,kafka3.internal:9092SP_KAFKA_REPLICATION_FACTOR=3SP_KAFKA_SASL_MECHANISM=SCRAM-SHA-512SP_KAFKA_USERNAME=speculaproSP_KAFKA_PASSWORD=<password>SP_KAFKA_TLS_ENABLED=trueSP_KAFKA_TLS_CA_FILE=/etc/speculapro/kafka-ca.pem
SP_REDIS_ADDRS=s1.internal:26379,s2.internal:26379,s3.internal:26379SP_REDIS_SENTINEL_MASTER=mymasterSP_REDIS_PASSWORD=<password>SP_REDIS_TLS_ENABLED=true
SP_NOTIFY_PUBLIC_URL=https://speculapro.yourbank.com.ghSP_NOTIFY_EMAIL_PROVIDER=smtpSP_NOTIFY_SMTP_HOST=smtp.yourbank.com.ghSP_NOTIFY_SMTP_USERNAME=speculaproSP_NOTIFY_SMTP_PASSWORD=<password>
SP_METRICS_ADDR=10.0.1.15:9090 # an internal interface only