User guide

Audit and Compliance

Looking for what it does rather than how to use it? Read the Audit trail overview .

What it is

Solidlio records who did what, to which record, from where, and when — across authentication, tenant administration, assets, changes, billing and integrations. The record is append-only, readable by administrators in their own portal, and exportable as a file an auditor can keep.

This guide covers the trail itself: what is recorded, who can read it, how to search and export it, and how retention and legal holds work.


Concepts

TermWhat it means
Audit entryOne immutable row: actor, action, target, timestamp, IP, user agent, and an optional before/after payload
ActorThe person who performed the action. Recorded as both a link and a copied-at-write-time email, so it survives deletion of the user
ActionWhat happened — LOGIN_SUCCESS, invoice.update, MANAGEMENT_LINK_ACCEPTED
EntityThe record acted on, as a type and an id — Invoice / inv_ab12…
ChangesThe before/after payload. Secret-bearing fields are stored as [redacted]
Tenant scopeEvery entry carries an organization, an account, or both. Reads are pinned to the caller’s own
Domain trailPurpose-built histories that sit alongside the general trail — change records, ticket activity, location history
Legal holdSuspends retention deletion for one tenant while a matter is live
Retention windowHow long entries are kept before the purge job removes them. Off by default — entries are kept indefinitely

The three trails, and why there is more than one

The tenant trail answers “what happened in my organization”. The platform trail answers “what did the operator of the platform do”. They are deliberately separate: a tenant administrator must never see another tenant’s activity, and platform-operator actions must be recorded somewhere a tenant administrator cannot influence.

The platform Audit Logs page reads both and labels each row with its source, so a platform administrator investigating an incident sees one timeline rather than two.


Roles and permissions

The floor for reading any audit trail is organization administrator.

RoleOwn org’s trailManaged clients’ trailsPlatform trail
platform administratorYesYesYes
MSP administratorYesYesNo
MSP technicianYesYesNo
organization administratorYesNo
power userNoNo
CUSTOMERNoNo

power user and CUSTOMER receive 403 Audit logs require an administrator role. This is deliberate: an audit trail exposes every other user’s login times, IP addresses and user agents, which is not information a standard user should hold about their colleagues.

The floor is organization administrator rather than a staff-only check because MSP technician outranks organization administrator in the role hierarchy — flooring at MSP technician would lock a tenant out of its own trail entirely.


Walkthroughs

Investigate what happened to a record

  1. Go to Settings → Audit Log (/org/settings/audit, or /msp/settings/audit in the MSP portal).
  2. Type the record’s id into Search events — the search matches action, entity type, entity id and actor email.
  3. Narrow the range with the date filter (last 24 hours, 7 days, 30 days, 90 days, or all time).
  4. Click the eye icon on a row to open the detail panel. It shows the actor, their role at the time, the originating service, the IP address, the user agent, and the full before/after payload.

Produce an evidence file for an auditor

  1. Go to Settings → Audit Log.
  2. Set the date range you have been asked for.
  3. Click Export and choose CSV or JSON.

The export is generated on the server and covers the whole date range you selected — up to 50,000 rows — not the page on screen. The CSV carries id, createdAt, actorEmail, actorRole, personId, action, entityType, entityId, ipAddress, userAgent, service and changes.

The export writes its own audit entry (AUDIT_LOG_EXPORTED) recording who exported, the format, the row count, the date range, and whether the 50,000-row cap was reached. Taking a copy of the trail is itself an event in the trail.

If truncated is true in the JSON export, narrow the date range and export in parts — the file is otherwise incomplete without saying so.

When you are on notice to preserve records — litigation, a regulator request, an incident investigation — a hold stops the retention purge from touching that tenant’s entries.

Releasing a hold sets releasedAt but never deletes the hold row, so “was this trail ever held, by whom, and why” stays answerable afterwards.

A hold with neither organizationId nor accountId is rejected — it would match nothing and the purge would run straight past it.


Configuration

SettingWhereDefaultEffect if unset
AUDIT_RETENTION_DAYSEnvironmentunsetEntries are kept indefinitely. Nothing is ever deleted

With the flag on and the number unset, the job runs, reports skipped: retention_not_configured, and deletes nothing. A retention job that defaulted to deleting would, on first deploy, destroy history nobody agreed to destroy.

The purge runs at 03:15 UTC, deletes in batches of 1,000, and stops after 50 batches — a ceiling of 50,000 rows per run — so a first run against a large backlog cannot monopolise the database. The remainder is reported in the run metadata and cleared on subsequent nights.


What is recorded

Authentication and account security

33 action types, written at 35 call sites across the auth routes:

LOGIN_SUCCESS, LOGIN_FAILED, LOGOUT, OAUTH_LOGIN_SUCCESS, OAUTH_LOGIN_FAILED, MAGIC_LINK_SENT, MAGIC_LINK_VERIFIED, TOKEN_REFRESH, PASSWORD_CHANGE, PASSWORD_RESET_REQUEST, PASSWORD_RESET_COMPLETE, MFA_SETUP_INITIATED, MFA_SETUP_COMPLETE, MFA_VERIFY_SUCCESS, MFA_VERIFY_FAILED, MFA_RECOVERY_USED, MFA_DISABLED, MFA_RECOVERY_REGENERATED, ACCOUNT_CREATED, ACCOUNT_VERIFIED, PROFILE_UPDATED, ACCOUNT_LOCKED, ACCOUNT_UNLOCKED, SESSION_CREATED, SESSION_REVOKED, ALL_SESSIONS_REVOKED, USER_CREATED, USER_UPDATED, USER_DEACTIVATED, USER_DELETED, USER_ROLES_GRANTED, USER_ROLES_REVOKED, AUDIT_LOG_EXPORTED.

The six USER_* actions cover the account lifecycle an access review is built on: provisioning, edits, deactivation, deletion, and every role grant and revocation. Updates carry a field-level before/after diff, and a role change records only the assignments actually written — a re-grant of a role the person already held does not appear as a new privilege.

Co-management

9 action types covering every transition of an MSP’s access to a client’s data: MANAGEMENT_INVITE_SENT, MANAGEMENT_INVITE_WITHDRAWN, MANAGEMENT_REQUEST_SENT, MANAGEMENT_LINK_ACCEPTED, MANAGEMENT_LINK_DECLINED, MANAGEMENT_LINK_REVOKED, MANAGEMENT_PRIMARY_CHANGED, MANAGEMENT_CLIENT_OFFBOARDED, MANAGEMENT_WENT_INDEPENDENT.

Both sides get a row. The client can show when an MSP’s access started and ended without asking that MSP for its logs.

Assets, network and locations

39 write sites covering asset create/update/delete, lifecycle transitions, sub-resources, and IPAM subnet and address changes, with field-level diffs over 29 tracked asset fields.

Billing and money movement

74 write sites across invoices, credit notes, contracts, quotes, purchase orders, vendor invoices, expenses and client credit lines. Creates record the key fields; updates record a field-level before/after diff; state transitions (send, void, approve, reject, terminate, reimburse) get their own action names. Scheduled billing writes carry actorRole: "SYSTEM" with the tenant pin taken from the source record, so a machine-driven invoice is attributed rather than anonymous.

Integrations and credentials

Entries record that a credential changed and never what it changed to: secret-bearing fields are replaced wholesale before the row is written.

Privacy

6 privacy action types — DATA_ACCESS, DATA_EXPORT, DATA_DELETE, CONSENT_GIVEN, CONSENT_REVOKED, CROSS_BORDER_ATTEMPT — recorded alongside the data subject, the legal basis, and the region the action occurred in.

Change management

A dedicated ITIL change record with 25 action types, including every approval decision, authority resolution and quorum event.

Platform operations

99 write sites across tiers, subscriptions, staff, finance settings, commissions, document templates, announcements, branding, fulfillment and platform quotes.


Troubleshooting

403 Audit logs require an administrator role The caller’s role is below organization administrator. Check the role on the membership being used — power user and CUSTOMER cannot read any audit trail.

403 Organization context required to access audit logs The request carried no organization. A person with no active organization membership cannot read a trail, because there is no tenant to pin the read to.

404 Audit log not found on an id you can see in the list The entry belongs to a different organization or account than the one your session is currently scoped to. Detail lookups are tenant-pinned, not id-only — switch portals or organization context and retry.

The Audit Log page is empty for a service you know made changes Not every service writes to the tenant trail. See “Coverage limits” below for the current list. Change requests have their own record on the change’s page; ticket field changes are on the ticket’s Activity tab.

Exported file has fewer rows than expected The export caps at 50,000 rows (tenant) or 10,000 per trail (platform). The JSON export reports truncated: true when the cap was hit. Narrow the date range and export in parts.

The actor shows as “System” The entry has no actor: it was written by a background job or an unauthenticated path (a failed login before the user was identified, for example). Entries with a real actor keep their email even after the user is deleted.

A legal hold was placed but rows were still deleted Check the hold’s scope. A hold on organizationId does not cover entries that carry only an accountId — co-management entries are written with an account and no organization. Place the hold on both keys, or add a second account-scoped hold.


Limits and known behaviour

The catalogue is shared by every tenant and has no per-product version history, so before these entries existed a product’s previous name, cost or visibility was recorded nowhere at all. Distributor sync writes are not audited — they are machine imports, not operator actions, and their run history lives in DistributorSyncLog.

Entries cannot be edited or deleted through any API. No route on any service exposes an update or delete verb for an audit row. The only path that removes entries is the retention purge, which is opt-in and respects legal holds.

Deleting a user does not delete their audit history. The actor link is cleared (personId becomes null) but the entry survives, and the actor’s email and role — copied in at write time — remain readable.

Deleting an organization does not delete its audit history. The organization and account keys on an audit entry are plain columns with no foreign key, so there is no cascade to follow.

Secrets are redacted, not encrypted. Fields whose names contain password, secret, token, apikey, credential, cvv, iban and similar markers are stored as the literal string [redacted]. Redaction replaces the whole value, so a secret nested under a matching key cannot survive in a sub-field.

The filter is name-based, and it errs in both directions:

  • A secret stored under an unrecognised field name is not caught.
  • A harmless field whose name merely resembles a secret — hasRefreshToken, apiTokenRotated, credentialKeys — has its value replaced too, which destroys evidence rather than protecting it. Name audit payload fields for what they are (rotated, hasRefresh, suppliedFieldNames) rather than after the secret they describe.

The entry still records the integration, the tenant, the timestamp and the originating IP; correlate it with the preceding integration.connect entry to identify who started the flow.

IP address and user agent are recorded on the tenant trail, not the platform trail. Platform-operator entries record the actor’s id and email but currently leave ipAddress and userAgent null at the 95 direct write sites that have not yet been converted to the helper. New platform audit writes that go through recordPlatformAudit() do capture both.

The platform list merges two tables and paginates after merging. Each side is read up to page × limit rows, so deep pagination costs more than shallow. The default page size is 50, capped at 100.

Retention is off by default, so audit tables grow without bound until an operator sets AUDIT_RETENTION_DAYS.

Questions this guide did not answer?

Ask us. You will get a reply from someone who uses the product every day.

Book a demo Contact us

A 30-minute walkthrough against your own workflow. No slides.