Ongoing monitoring

Background jobs

The API is not only request/response. Eleven workers run inside the same process: they keep watchlists current, collect adverse media, re-screen monitored subjects, deliver webhooks and age out data. This page states when each runs, which are off by default, and what you can control at runtime.

Several jobs are OFF until you switch them on. That is deliberate and marked per job below. A job you believe is running and is not is worse than one you know is off.

What runs, and when

JobRunsDefaultConfig key
Watchlist ingestionscheduled + on demandonWatchlists:*
Adverse-media prefetch (DOC)every 15 min, 1 subject / 60 soffAdverseMedia:Prefetch:Enabled
Adverse-media slice tail (GKG)every 15 min, follows the feedoffAdverseMedia:SliceTail:Enabled
Scheduled re-screenpoll 60 min · daily 24 h · full 30 doffMonitoring:Enabled
Batch processingcontinuous, leases queued batchesoffBatch:Enabled
Coverage auditevery 12 h (first run 2 min after start)onCoverage:*
Retention purgeevery IntervalHoursoffRetention:Enabled
Webhook capture purgehourly, 24 h TTLalways on
Webhook outbox deliverycontinuous; retries 5 m → 1 h → 5 h → 18 honWebhooks:*
Triggered job runnerpolls 5 s, heartbeat 30 son
Warm-uponce at startupon

Two to check before going live

Retention purge is off by default. Retention:Enabled defaults to false, so nothing ages out screening records until you set it. Adverse-media evidence is criminal-offence data about identifiable people (GDPR Article 10) — decide your retention period and enable this deliberately.
Adverse media is off by default, and a miss is not a pass. With prefetch and slice-tail disabled an adverse-media screen returns Unavailable, never “clean”. That is the honest answer — we did not check — and it must not be read as nothing found.

The webhook capture purge has no off switch, on purpose. It deletes captured payloads after 24 hours, and those payloads contain screening results. A configurable purge is one somebody eventually disables, turning a debugging convenience into an indefinite store of personal data.

Starting a job on demand

Long-running jobs start without a restart. Every trigger is single-flight: a second request while one is running returns 409 Conflict rather than starting a duplicate.

EndpointStarts
POST /v1/admin/watchlists/ingestFull watchlist re-ingest
POST /v1/admin/jobs/retagRe-tag in place (no re-fetch)
POST /v1/admin/jobs/coverage-auditCoverage audit
StatusMeaning
202 AcceptedQueued — poll the status endpoint
409 ConflictAlready running. The single-flight guard, not an error
403 ForbiddenYour account lacks platform_operator
429 Too Many RequestsTrigger rate limit — Retry-After says when
Triggering requires the platform_operator role. It is deployment-wide and deliberately not implied by admin, so a tenant administrator cannot start a job that affects every tenant.

Observing

EndpointShows
GET /v1/jobs/statusRunning jobs with progress
GET /v1/ingest/historyPast runs: when, duration, outcome, counts
GET /v1/coverageLive PEP coverage matrix and when it was measured

A job that is off shows as off — the status endpoint does not invent a schedule for a disabled worker. Trust it over the configuration file you edited: setting a key in a manifest is not the same as it reaching the process.

Behaviour under failure

  • Single-flight is enforced in the database, not in memory: a partial unique index allows one queued/running row per job name, so two instances cannot both start it. In-process locks would not survive scale-out.
  • A job whose process dies is resumed, not orphaned. Long jobs hold a lease and heartbeat it every 30 s; a lease that stops beating is reclaimed and work continues from its cursor.
  • Webhook delivery is a durable outbox, off the request path. A screen never waits on your endpoint. Retries run 5 m → 1 h → 5 h → 18 h, each carrying the same eventId so you can deduplicate.
  • Rate limits back off rather than spin. The adverse-media collectors treat a 429 as a cooldown (300 s, growing to a 1800 s cap).

Configuration

{
  "AdverseMedia": {
    "Prefetch": {
      "Enabled": false,
      "PollMinutes": 15,
      "DelaySecondsBetweenSubjects": 60,
      "MaxSubjectsPerRun": 500
    },
    "SliceTail": { "Enabled": false }
  },
  "Monitoring": { "Enabled": false, "PollIntervalMinutes": 60,
                  "DailyIntervalHours": 24, "FullIntervalDays": 30 },
  "Retention":  { "Enabled": false, "DefaultDays": 1825,
                  "IntervalHours": 24, "BatchSize": 500 },
  "Batch":      { "Enabled": false },
  "Coverage":   { "IntervalHours": 12 }
}

Every key is settable by environment variable using the standard double-underscore form:

AdverseMedia__Prefetch__Enabled=true
Retention__Enabled=true
Monitoring__Enabled=true

Resource profile

All eleven run in the API process. There is no separate worker, no scheduler service and no message broker to operate.

JobCost
Slice tail~15 MB per 15 min; parsing is ~0.018% of one core
Prefetchone outbound call per minute while enabled
Coverage audita few queries, twice a day
Retention purgeBatchSize rows per pass, throttled
Triggered job runnerone lightweight poll every 5 s

The heavy job is a full watchlist ingest — which is why it is on-demand and single-flight rather than frequent.