Skip to content

Audit log

The audit log is SpeculaPro’s logbook. Every significant action (signing in, approving a rule, closing a case, viewing someone’s full personal details, creating an API key) is written to it along with who did it, when, and from where.

It is append-only: entries can be added, but never changed or deleted. The database itself refuses any attempt to edit or remove an entry, even from SpeculaPro’s own code. The attempt fails with an error such as UPDATE on audit_log is not allowed: table is append-only.

An entry is written in the same database transaction as the action it describes. If the entry can’t be written, the action is cancelled too, so there is never an approved rule without a record of who approved it.

Field Meaning Example
When Date and time 2026-10-08 14:32:07
Who The actor: a person, an API key, or the system itself. On the hosted service also SpeculaPro’s staff, shown by name as SpeculaPro — name (actor type provider) or SpeculaPro support — name (actor type support) [email protected]
What The action rule_version.approve
On what The kind of record and its identifier rule version 0192…
Before / after The relevant values before and after a change, where it applies old and new threshold
Where from IP address and browser or client 102.176.x.x, Chrome
Request A request identifier linking the entry to technical logs

Recorded today:

Action When it is written
auth.login Successful sign-in; records method (password, passkey, sso_oidc or sso_saml), whether multi-factor was used, for a password sign-in the second_factor (totp or recovery_code), for a passkey sign-in the passkey_id, and, for single sign-on, the identity provider’s issuer and subject
auth.login_failed Failed sign-in attempt; records the reason, such as bad_password, bad_totp, bad_recovery_code, bad_step_up_code (a wrong authenticator code when making new recovery codes or adding a passkey), locked_step_up (the same check refused because the account is locked), bad_passkey, passkey_unverified (the device didn’t check the person), passkey_cloned (the passkey’s counter went backwards, which suggests a copy), account_unavailable, temp_password_expired, bad_totp_after_sso (a wrong authenticator code after single sign-on), locked_totp_after_sso (the code step refused because the account is locked) sso_required (a password sign-in refused because the workspace requires single sign-on; it doesn’t count towards the lockout) or, on the hosted service, workspace_suspended
auth.logout Sign-out
auth.password_changed Password changed
auth.totp_enabled Authenticator app set up
auth.recovery_codes_generated A new set of recovery codes made, at set-up or from Settings → Sign-in; the old set stops working. The codes themselves are never recorded
auth.recovery_code_used A recovery code used to sign in instead of the authenticator code; records how many remain
auth.passkey_added, auth.passkey_removed A passkey added to or removed from a person’s account; records its name, and for an added one whether it is synced
api_key.create, api_key.revoke API key created or revoked. A revoke records reason: rotated when the key was replaced, or automatic: true and reason: creator_deactivated when the workspace revokes a deactivated person’s keys
api_key.rotate A key replaced by a new one with the same name, scopes and expiry; recorded on the new key, naming the key it replaces
api_key.rotation_due A working key marked rotation due because the person who created it was deactivated; records the person. Recorded as the system, naming the SCIM token, when the identity provider deactivated them
tenant.create A new workspace provisioned (recorded as the system), with its jurisdiction when one was given with --jurisdiction. On a multi-region service it also records the region
tenant.settings Workspace settings changed, including the jurisdiction and identifier checks; records every setting before and after
workspace.move.start, workspace.move.seal A move to another region began (the workspace became read-only), or the workspace was sealed before its export; recorded as the system with the operator’s name, the move and the target region. These travel with the workspace
workspace.move.export, workspace.move.import The workspace exported from the source region, or loaded and verified in the target; records the operator, rows, tables and the manifest’s SHA-256, and for an export whether the bundle was encrypted (encrypted) and whether the check for pending work was skipped (pending_check_skipped). Kept by that region at platform level, so they don’t appear in the workspace’s audit log
workspace.move.rebuild After an import, what became of the workspace’s counters in the target’s Valkey (features) and of its analytics copy (analytics): rebuilt with the counts, cold, failed with the reason, or none when the cell has no analytics store. Platform level, in the target
workspace.move.flip The workspace went live in the target region; records the regions and the operator
workspace.move.handover, workspace.move.purge The source region ended the workspace’s sessions (sessions_ended) and began pointing clients at the new region, then deleted its copy; the purge records whether changes made after the export were deleted with it and how many Valkey keys it deleted (feature_keys). Platform level, in the source
workspace.move.abort A move undone before the flip
jurisdiction.import A signed jurisdiction bundle imported from the command line (entity type jurisdiction; recorded as the system, for the whole installation, so it doesn’t appear in a workspace’s audit log); records the jurisdiction, the version before and after, the name, source: bundle, the signing key and the bundle’s digest. An import that changes nothing writes no entry
rule_pack.install A rule pack installed; records the pack, its version and the rules added, and for a signed bundle also source: bundle and the signing key_id
rule_pack.downgrade Someone with rules.packs_downgrade forced an older signed pack file; records the old and new versions, the rules added, report (the outcome for every rule, as for an upgrade), source: bundle, the key_id and forced: true
datasource.create, datasource.update A data source added or changed (pausing and resuming are changes to enabled); records the name, kind, item kind, format, settings, schedule and whether the credentials changed. The credentials themselves are never recorded
datasource.delete A data source deleted; records its name
datasource.delete_forced A Postgres replication source deleted although its replication slot still existed (?force=true in the API); records its name and the slot left behind on the institution’s database
datasource.test A data source’s connection tested; records whether it worked and the message
datasource.mapping A new mapping version was saved for a change data capture source; records a summary: the name, version, note, the tables (up to a limit) with what each becomes, the table and field counts, and the mapping’s size and SHA-256. The mapping itself stays in its version history
datasource.cdc_resync Someone selected Read everything again on a change data capture source
datasource.cdc_drop_slot Someone asked for a Postgres source’s replication slot to be dropped
datasource.run_now Someone asked a data source to collect now
datasource.results_download A file’s results downloaded (entity type data_source_file); records the file name
datasource.file_processed A collected file finished loading, was refused, or was failed after its load stopped three times (recorded as the system; entity type data_source_file); records its name, size, SHA-256, format, status, the row counts (accepted, duplicate, stale, rejected, retry), any error, where its results went and, for a file failed after interrupted loads, its attempts. A file refused without being read, because it was too large, has no entity ID
workspace.offboard An operator ran speculapro tenant offboard (recorded as the system, user agent speculapro-cli, entity type tenant); records the workspace’s slug and name, the operator, how many reports and filed exports were written, the manifest’s SHA-256 and whether the workspace was deleted. When the workspace is deleted, it is a platform entry (no workspace, tenant_id empty; the entity is the deleted workspace’s ID), so it stays in the audit log after the workspace is gone. See Offboarding a workspace
license.install A licence installed from the web app; records the licence ID, licensee, edition, expiry and signing key (key_id). Installs with the speculapro license install command aren’t in the audit log
rule_pack.upgrade An installed pack upgraded; records the old and new version, the rules added and report: the outcome for every rule that changed (added, updated with a new version awaiting approval and your parameters kept, customised, needs attention). From a signed pack file it also records source: bundle and key_id
rule.create A new rule created
rule.schedule A scheduled rule’s timetable changed; records the schedule before and after
rule_version.create A new draft version; records the previous version and parameters, and the new draft
rule_version.submit A draft sent for approval
rule_version.approve, rule_version.reject A second person’s decision, with their note
rule.deploy A version put into the active or shadow slot; records what was there before
rule.clear_shadow The shadow slot emptied
backtest.start A backtest queued; records the rule, the version, whether parameters were overridden, the definition hash, the period, the rule’s mode and, for a scheduled rule, the schedule simulated
backtest.cancel A backtest cancelled; records its status at that moment (cancelled if it was queued, running if it was asked to stop)
backtest.finish A backtest ended (recorded as the system); records its status and, when it succeeded, its hits, alerts and events replayed (for a scheduled rule, the number of simulated runs), or the error
rule.enable, rule.disable A rule switched on or off
ai.alert_review The AI assistant was asked about an alert, by a person or (recorded as the system) automatically; records the review, its recommendation and confidence, the model, whose key was used and the tokens. The advice changes nothing about the alert
ai.case_summary The AI assistant was asked by a person to summarise a case; records the summary’s ID, the assistant’s suggestion and confidence, the model, whose key was used and the tokens. The summary and its suggestion change nothing on the case and are not on its timeline
ai.batch A person asked the AI assistant about many alerts or cases at once; recorded once for the batch with the kind (alert_review or case_summary), how many were queued (asked) and whether it was everything without advice (unadvised). Each answer of the batch then has its own ai.alert_review or ai.case_summary entry under the same person, with the request ID ai-batch: followed by the batch’s ID
ai.narrative_draft The AI assistant was asked by a person to draft a report’s narrative; records the draft’s ID, the model, whose key was used and the tokens, never the text. The report is not changed by it; using the draft shows as an ordinary report.update
ai.rule_tuning The AI assistant was asked by a person for tuning suggestions on a rule; recorded on the rule with the suggestion record’s ID, the rule version read, how many suggestions came back, the model, whose key was used and the tokens, never the text. The rule is not changed
ai.allowance_request An administrator asked the service provider for more AI assistant calls this month; records the number asked for, not the note
ai.settings_update The AI assistant’s settings changed; records on or off, the key’s source, the provider and API address, the model, automatic review and whether the workspace’s own key was changed. Never the key. Switching it off is written here at once; a change that needed approval also has the usual approval entries
alert.close An open alert dismissed; records the disposition and note. When it was closed together with others, each alert has its own entry, with bulk: how many alerts the request named
case.create A new case opened from an alert; records the case number, priority, the alert it came from and who it is assigned to. When an escalation policy fitted the case, also the policy’s name, the level the case opened at and the team whose queue it joined
case.link_alert An alert added to a case. By a person: opening a case from an alert, or adding it to the subject’s active case. By the system: a new alert linked automatically to the subject’s active case, or new activity that raised the case’s priority (the entry then records the new priority)
case.comment A comment added to a case; records the text
case.assigned A case assigned or unassigned; records the new assignee
case.status_changed Work started on a case
case.escalated A case escalated; records the levels, new priority and reason, and the policy and team when the case follows a policy. By the system with auto: true and the reason deadline passed when the policy escalated the case after a missed deadline
case.sla_breached A case passed its deadline while still open (recorded as the system, once for each deadline); records the deadline and the case’s level
case.team_assigned A case put in a team’s queue, or taken out of it; records the team
team.create, team.update, team.delete A team created, changed or deleted; records its name and members
escalation_policy.create, escalation_policy.update, escalation_policy.delete An escalation policy created, changed or deleted; records the policy as saved (for a deletion, its name)
case.attachment_added An evidence file added to a case; records the file’s name, size and SHA-256
case.attachment_download An evidence file downloaded; records the file’s name and SHA-256. Written for every download
case.attachment_removed An evidence file removed: its content is deleted and its record kept; records the name, SHA-256 and reason
case.approval_requested A closure proposed for a second person’s approval; records the disposition and note
case.disposition_set A case closed directly; records the disposition and note
case.approved, case.rejected The second person’s decision on a proposed closure
case.reopened A closed case reopened; records the reason
user.invite A person added to the workspace; records their email and roles. The account starts as Invited and the temporary password lasts 72 hours. The password itself is never recorded. Records sso: true for a passwordless invitation in a workspace that requires single sign-on
user.roles A person’s roles changed; records the roles before and after, and via: sso or via: scim when they were set from identity-provider groups (the actor is then the system). Lists privileged_roles_withheld and privileged_roles_kept when SCIM left privileged roles out or in place
role.create, role.update, role.delete A custom role created, changed or deleted; records the role before and after, with its permissions
change_request.propose A change sent for approval instead of being made (entity type change_request); records the kind of change, the operation, the target, the summary, the maker’s reason, and the target before and after as proposed. Credentials never appear; a single sign-on client secret is represented only by a keyed fingerprint, so a change to it shows without revealing it
change_request.approve A second person approved a request and the change was applied; records the checker’s note and the result. The change itself is recorded under its own action (such as user.roles) with the maker as the actor
change_request.reject A second person rejected a request; records their reason
change_request.withdraw The maker withdrew their request; records their note, if any
change_request.stale A request found out of date when someone tried to approve it, because its target had changed; nothing was applied
change_request.expire A request nobody decided within 7 days, closed by the worker within about a minute of its expiry (recorded as the system)
change_request.secret_collected The maker collected the one-time secret an approved request produced (a temporary password, API key or webhook signing secret); records its label, never the secret
approval_policy.update Someone changed which changes need approval; records each kind of change before and after
approval_policy.default A default requirement took effect because two people can now approve that kind of change (recorded as the system); records the kind of change
user.disable, user.enable A person’s access switched off (which also signs them out everywhere) or back on. Over SCIM, recorded as the system with via: scim; a SCIM deactivation also records sessions_ended. A deactivation records api_keys_rotation_due and api_keys_revoked counts when the person had created working API keys
user.reset_2fa A person’s authenticator app reset, so they must set it up again; their recovery codes and passkeys are deleted and their sessions end
user.reset_password A temporary password issued; their sessions end and the account is unlocked. When an operator used the speculapro tenant reset-password command, the actor is the system and the entry records via: cli
auth.sso_failed A single sign-on attempt refused; records the reason (for example email_domain_not_claimed, not_provisioned, no_role_mapping, email_not_verified, user_disabled, idp_mfa_missing, state_browser_mismatch, saml_signature_invalid, saml_audience_mismatch, assertion_replayed, bad_totp, workspace_suspended, and plan_user_limit when the hosted plan’s number of people is reached so no just-in-time account was made), a technical detail and the protocol. Failures before the workspace is known (such as an unknown or expired state, or an unknown workspace) are recorded too, as installation-level entries with no workspace (tenant_id empty) and workspace_known: false; they appear only in the database table, not in a workspace’s Audit log. See Why a sign-in failed
sso.config_update Single sign-on settings changed; records every setting before and after. The client secret appears only as (changed), pasted metadata only as its size
sso.metadata_refresh SAML identity-provider metadata fetched again by an administrator; records the URL
sso.domain_add, sso.domain_verify, sso.domain_remove An email domain claimed, verified by its DNS record, or removed; records the domain
sso.break_glass The break-glass administrators changed; records their emails before and after
sso.test, sso.test_failed A connection test succeeded (records the protocol, subject and email) or failed (records the reason)
sso.identity_linked A first single sign-on linked to an existing account; records the identity provider’s issuer, the subject and the email. Recorded as the system
sso.user_provisioned An account created at a first single sign-on; records the email, roles and issuer. Recorded as the system
sso.roles_sync_skipped Roles from identity-provider groups not applied because they would leave the workspace without an administrator, or because a privileged role was mapped only by a group pushed over SCIM and the workspace doesn’t let SCIM give privileged roles (privileged_roles_withheld). Recorded as the system
auth.sso_logout A signed SAML logout request from the identity provider ended sessions (recorded as the system); records the IdP, the person’s NameID, the sessions named, how many ended and whose
auth.sso_logout_failed A SAML logout request refused; records the reason (for example slo_unsigned, slo_signature_invalid, slo_request_expired, slo_request_replayed) and a technical detail. Before the workspace is known it is an installation-level entry
scim.token_create, scim.token_revoke A SCIM provisioning token created or revoked (entity type scim_token); records its name, prefix and expiry. The token itself is never recorded
scim.user_provisioned An account created over SCIM (recorded as the system, as are all changes made over SCIM, with via: scim and the token’s name and ID); records the email, name, roles, the IdP’s identifier and whether it was created active. Lists privileged_roles_withheld when SCIM left privileged roles out
scim.user_updated A person’s sign-in email, name or IdP identifier changed over SCIM; records before and after
scim.group_created, scim.group_updated, scim.group_deleted A group pushed, changed or deleted over SCIM (entity type scim_group); records its name, the members added and removed, and the roles its name maps to
scim.roles_sync_skipped Roles from SCIM groups not applied, because the person is a break-glass administrator or the change would leave no administrator; records the reason and both sets of roles. Also written when the only roles asked for were privileged ones SCIM may not give
scim.privileged_roles_update Let SCIM give privileged roles switched on or off; records before and after
report.create An STR drafted from a case (records the number, type, case and number of transactions), or a CTR drafted by the system (records the number, business day, total and transactions). Entity type report, as for all report.* actions. See Regulatory reporting. A CTR draft whose selection changes later isn’t recorded again as report.create; its edits are report.update
report.view_pii Someone opened a report with its identity details decrypted. Written at most once per person and report every 30 minutes
report.update A draft edited; records which fields changed and, if the selection changed, how many transactions remain. The list of attachment titles counts as a field (attachments), and the choice of evidence files that go with the report as another (files)
report.request_approval A draft sent for approval
report.return A report sent back to draft; records the note
report.void A report voided before filing (draft, awaiting approval or approved); records from (its status before), to: voided and the reason. The report stays in the register
report.approve A report approved by a second person; records the optional note
report.export The report’s file made (goAML XML, or the CENTIF declaration archive); records the format, SHA-256 and size
report.download The goAML file downloaded; records its SHA-256
report.package_download The filing package (a ZIP of the export, the evidence files chosen for the report and a manifest) downloaded; records the export’s SHA-256 and the names of the files in it
report.submit The filing with the FIU recorded (the upload to goAML, for that format); records the FIU’s reference and submission time
report.acknowledged, report.rejected The FIC’s answer recorded; records the note
reporting.settings Settings → Reporting changed (entity type tenant); records every value before and after
user.notify_prefs A person changed their own notification settings (email, SMS, work phone, and whether WhatsApp messages are on) under Settings → Notifications
user.whatsapp_code_sent Someone asked for a WhatsApp verification code; records the number it went to (E.164), never the code
user.whatsapp_verified Someone confirmed their WhatsApp number with the code; records the number. WhatsApp messages are switched on
user.whatsapp_removed Someone removed their WhatsApp number; WhatsApp messages stop
notify.whatsapp_test An administrator sent a WhatsApp test message to their own verified number
notify.whatsapp_provider_changed A worker started with a different WhatsApp provider, GOWA address or device ID from the last one (recorded as the system, entity type notify_channel, no workspace); records the provider, the base_url (without credentials, query or fragment) and the device_id, before and after. Nothing is written the first time, when the provider is none
customer.view_pii Someone with entities.view_pii opened a customer’s profile with the protected fields (phone, email, national ID, date of birth) decrypted. Written at most once per person and customer every 30 minutes, so page refreshes don’t flood the log
designation.create A PEP, sanctions or other designation added; records the subject, type, class, reason, start (when later than now), end date and any evidence
designation.revoke A designation revoked; records the reason
suppression.propose A suppression proposed; records its scope, rule, subject, reason and length in days
suppression.approve A suppression approved by a second person
suppression.reject A proposed suppression rejected; records the reason
suppression.revoke A suppression ended early, or withdrawn while pending; records the reason if one was given
list.create A list created; records its key, name, kind and item type
list.add_items Values added to a list; records the values (after cleaning), the note and any end date
list.remove_item A value removed from a list; records the value
list.item_expiry A value’s end date set or cleared; records the value and the new date
list.update A list renamed or its description changed; records the name and description before and after
list.delete A list deleted; records its key and how many values it held
webhook.create A webhook endpoint registered; records its address and events. The signing secret is never recorded
webhook.update A webhook endpoint changed, switched off or switched on; records the fields that were sent (url, description, events, enabled)
webhook.delete A webhook endpoint deleted; records its address
webhook.rotate_secret A webhook’s signing secret replaced; records how long the previous secret keeps signing (overlap, for example 24h0m0s)
webhook.resend A webhook delivery sent again; the entry points at the original delivery
webhook.test A test event sent to a webhook endpoint; records the test delivery and event IDs
decision.feedback A real-time decision’s outcome reported or labelled (final status, fraud or legitimate, fraud type, step-up result, note); records the feedback before and after. The actor is the person who labelled it, or the API key that sent POST /api/v1/feedback. Sending the same feedback again writes nothing. See Feedback and fraud labels
privacy.settings_update Data protection settings changed; records the jurisdictions, hosting, DPO name and registration before and after
privacy.retention_update Retention periods or end-of-period actions changed; records each changed category before and after, and purge_enabled: false when shortening a period switched the purge off
privacy.purge_switch The retention purge switched on or off
privacy.dry_run A dry run; records what it counted per category
privacy.purge A purge run finished (recorded as the system); records its status, what it deleted, pseudonymised or kept per category, and any error
privacy.partition_drop A whole monthly partition dropped by the purge (recorded as the system); written once for each workspace with rows in it, and once with no workspace listing them all
privacy.legal_hold_set, privacy.legal_hold_release A legal hold placed or released; records the scope, target, reason and reference, or the release reason
privacy_request.create A data subject request registered; records its number, kind, law, customer and whether it was flagged for the MLRO
privacy_request.update A request assigned, its identity check updated, its deadline extended, or withdrawn
privacy_request.clock_pause, privacy_request.clock_resume A request’s clock stopped while waiting for the requester’s identity or a clarification, or restarted (UK law only); records the reason, how long it was stopped and the due dates before and after
privacy_request.tipping_off_flagged An open request flagged for the MLRO after it was registered, because the repeated check found it now needs review (recorded as the system). The entry never says why
privacy_request.mlro_review The MLRO reviewed a flagged request; records their note
privacy_request.complete A request closed with its outcome; records the decision and legal basis
privacy_request.erasure_decision An erasure request decided; records the decision, legal basis, erasure date, whether a hold postpones it, and rows affected
privacy_request.erasure_postponed A due erasure became postponed by a hold, an open case, an open screening match or a report (recorded as the system); records the erasure date
privacy_request.erasure_hold_ended A postponed erasure stopped being postponed without being carried out yet (recorded as the system)
privacy_request.erasure_rescheduled A scheduled erasure’s date moved (recorded as the system)
privacy_request.erased A scheduled erasure carried out (recorded as the system)
privacy_request.export A person’s data exported for an access or portability request; records how many accounts, transactions and events, the size, and that it is complete. A customer.view_pii entry is written with it
privacy.activity_save, privacy.activity_delete A processing activity added, changed or removed
privacy.lawful_basis_set A data category’s lawful basis set
privacy.transfer_save, privacy.transfer_delete A cross-border transfer added, changed or removed
privacy.breach_create, privacy.breach_update A personal data breach recorded or changed; a new breach records the number, title, laws, risk, discovery time and people affected; a change records the status, risk, laws, people affected and discovery time before and after
privacy.breach_notified A breach notification recorded as sent; records when and the reference
privacy.breach_task_add, privacy.breach_task_done A breach task added, or ticked or unticked
privacy.breach_export A breach report for the regulator downloaded
screening.settings The screening settings saved: screening on or off, the match threshold, which subjects are screened and the lists; records the settings before and after
screening.hits.close Open potential matches were closed because the workspace stopped screening against their list, in the same save as the settings change; records the reason (source_removed), how many (hits) and the lists now screened (sources). Provider matches are not closed
screening.hit.assign A potential match assigned to a reviewer, or taken back (entity type screening_hit); records the new and previous assignee
screening.hit.comment A comment added to a match’s timeline; records its length, not its text
screening.hit.clear A match cleared as a false positive; records the list, the entry, the kinds, the reason and the good-guy entry
screening.hit.confirm A match confirmed as a true match; records the list, the entry, the kinds, the reason, the designation set or already in force, and the case and alert
screening.good_guy A good-guy entry recorded with a cleared match; records the list, the entry and the reason
screening.bundle.accept_removal Someone imported a signed list bundle through the API with accept_removal=true (entity type screening_list_version); records the list, the version number, how many entries were removed and remain, and the bundle’s SHA-256
alert.raise An alert raised for a finding a person established, today a confirmed screening match; records the kind, severity and source (screening.match)
screening.rescreen Someone started a full re-screen (entity type screening_run)
screening.screen_name Someone screened a name on demand (entity type screening_query). Written before any provider is called; if it can’t be written, nothing is sent. Records a keyed fingerprint of the name (name_hmac: an HMAC with the workspace’s key, so the same name gives the same value and the name can’t be worked back from it), the kind, whether a date of birth and an ID number were given (with_date_of_birth, with_id_number), the countries, the threshold, the lists screened and the providers the name was sent to. Never the name itself, the date of birth or the ID number
screening.screen_subject Someone screened one customer or counterparty on demand (the entity is that customer or counterparty); records the lists, the providers asked, the number of potential matches (hits) and whether a provider could not be asked (incomplete)
screening.list.version A new version of a sanctions list was stored (entity type screening_list_version); records the list, version number, entries and the counts added, changed and removed. Recorded under the person and workspace for an import through the API: one of the workspace’s own lists, or a signed bundle. Versions the worker fetches, provider feeds, and bundles and list files the operator imports from the command line, are recorded by the system with no workspace and don’t appear in a workspace’s audit log
screening.source.url The operator set or reset the download address of a fetched list from the command line (entity type screening_list_source); records the list and the address (empty when reset). Recorded by the system with no workspace
screening.provider.save, screening.provider.delete A screening provider added, changed or removed; a save records the provider, its address and dataset or group, whether it is on and whether the credentials changed. The credentials themselves are never recorded. An installation-wide provider the operator sets or removes from the command line is recorded by the system with no workspace, with installation: true and, on a save, whether its data feed is on (sync)
model.train A risk model training queued (entity type risk_model); records the version, the training window, the parameters, the seed, the number of features and the note. Recorded as the system when queued from the command line. A training queued by the retraining schedule is also recorded as the system, with schedule set to weekly or monthly
model.train_cancel A queued or running training cancelled; records its status
model.delete A version whose training failed or was cancelled deleted; records the version and its training status. A trained version is never deleted
model.shadow, model.unshadow A trained draft moved to shadow, or a shadow model moved back to draft; records the version and the state before and after
model.promote A shadow model made live by an approved change request; records the version and, when it replaced one, replaced_version
model.retire A model retired; records the version, the state it was in and the reason. Also written for the version a promotion replaces, with the reason Replaced by version N
model.schedule A workspace’s risk-model retraining schedule changed (entity type risk_model_schedule); records the frequency and training window before and after, and the next occasion. A save that changes nothing writes no entry
support.requested On the hosted service: a member of SpeculaPro’s staff asked for support access (entity type support_access); records the reason, ticket reference, and the scope and minutes asked for. Actor shown as SpeculaPro — name
support.approved, support.denied, support.revoked Someone in the workspace approved (records the scope and minutes given), denied, or ended a running access
support.cancelled, support.ended The member of staff withdrew the request before a decision, or gave a running access back early
support.handoff_issued, support.session_started, support.session_ended A one-time link was issued; a support session was opened from it; the session was signed out
support.read One read made in a support session; records the method and the address read. Actor shown as SpeculaPro support — name (actor type support)
support.refused A request a support session was refused: a write, a read outside its scope, or a route no session may read; records the reason and a count. The first refusal a minute per route is written at once, the rest as one entry when the minute closes
support.expired A request nobody decided within 72 hours lapsed, or an access ran its time. Recorded as the system
support.policy_changed The workspace’s support access policy changed; records before and after
provider.workspace_suspended, provider.workspace_reactivated SpeculaPro suspended or reactivated the workspace (actor type provider, with the name of the member of staff whose approval applied it). See A suspended workspace
provider.ai_topup, provider.ai_request_declined SpeculaPro added AI assistant calls for a month (records the month and the number), or declined the workspace’s request for more (records the number asked for)
provider.plan_changed, provider.deletion_scheduled, provider.deletion_cancelled SpeculaPro changed the workspace’s plan or limits, scheduled its deletion (records delete_after), or cancelled that
provider.notice_posted, provider.notice_updated, provider.notice_withdrawn A service notice addressed to the workspace was posted, changed or withdrawn

See Managing rules, Alerts and cases, Risk designations, Suppressions and Lists for what each of these steps means. Each case action also appears on the case’s own timeline.

Not recorded:

  • The decisions themselves. Each one is kept in the decision log with its reasons instead. A decision shows only its latest label; earlier labels are in the decision.feedback entries.
  • Automatic disabling of a webhook endpoint. The endpoint’s delivery log and the Webhook endpoint disabled notification show it.

Reading the audit log needs the audit.view permission. In the default roles, MLRO / Compliance officer, Auditor and Administrator have it.

  1. Go to Settings → Audit log.
  2. Narrow it with the area list (for example People, Roles, Change requests, Workspace settings, Rule versions, Cases, Reports, API keys) or by typing the start of an action, such as user. or change_request..
  3. Select an entry to see who did it, when, from where, and its before and after values.

Entries are newest first. Every change request is also kept, with its before and after, under Approvals → All.

Settings, Audit log tab: Append-only, with an area filter set to Everything and an action filter; entries newest first, such as auth.login by Abena Mensah, sso.domain_add and sso.config_update by Ada Owusu, approval_policy.default by SpeculaPro, and notify.whatsapp_provider_changed by SpeculaPro Settings, Audit log tab: Append-only, with an area filter set to Everything and an action filter; entries newest first, such as auth.login by Abena Mensah, sso.domain_add and sso.config_update by Ada Owusu, approval_policy.default by SpeculaPro, and notify.whatsapp_provider_changed by SpeculaPro

To read it from a script, use GET /api/v1/audit with the same filters; see Change requests API: the audit log. On SpeculaPro SaaS that is the way to export it.

How to read the audit log from the database (self-hosted)

Section titled “How to read the audit log from the database (self-hosted)”
  1. Connect to the SpeculaPro database with psql as the owner account (speculapro_owner). The app account sees nothing here without a workspace set; see Tenant isolation.

  2. List the latest entries for your workspace, newest first:

    SELECT a.created_at, a.actor_type, u.email, a.action, a.entity_type, a.entity_id, a.ip
    FROM audit_log a
    LEFT JOIN users u ON u.id = a.actor_id
    WHERE a.tenant_id = (SELECT id FROM tenants WHERE slug = 'your-workspace')
    ORDER BY a.created_at DESC
    LIMIT 50;
  3. To see exactly what changed in one entry, select its before and after columns, which hold the values as JSON.

Times are stored in UTC. Entries made by the system have no user, so email is empty. Entries that belong to no workspace (platform actions) have tenant_id NULL, so a query filtered by workspace leaves them out.

The audit log is split into monthly partitions, like other large tables. The worker role creates next months’ partitions ahead of time (see Worker).

SpeculaPro removes audit entries only through the retention purge, and only as whole months: a month’s partition is dropped once every workspace with entries in it has the purge switched on and is past its audit log period, and nothing in it is held. Entries without a workspace (platform-level entries such as offboarding records) are kept ten years. The audit log period can’t be set below the AML minimum for the workspace’s laws (five years in Ghana). Each dropped month is itself recorded as privacy.partition_drop.

Without the purge, nothing is removed. A database administrator can still archive an old month (for example export it, then detach the partition) without affecting recent entries; check your regulator’s minimum retention period first.

  • Regulators and internal audit can verify that procedures were followed: that rules were approved by someone other than their author, and that cases were closed properly.
  • Security teams can investigate suspicious activity: who viewed this customer’s details, and when.
  • Everyone is protected: a permanent record of who did what protects honest staff as much as it deters misuse.