Skip to content

Documentation

Not everyone who needs the trail should see all of it.

Usually the question that decides whether a company centralises its change history at all. At forty people, "everyone sees everything" means the support desk can read payroll.

Who read the trail

Your audit trail is the most sensitive dataset you keep here — it is every change, including the ones involving people’s data. So every search, every record opened in full and every export is itself recorded, and the record is yours to read at Settings → Who read the trail.

"Who looked at this" is a question SOC 2 and KVKK both ask, and it is the one most audit products cannot answer about themselves. It is also the reason some teams will not centralise their change history anywhere: they trade one risk for another and nobody offers them a way to watch the second one.
Exports are the row that mattersA search shows fifty rows on a screen to somebody already signed in. An export puts an unbounded number of them in a file that leaves the platform, with the row count recorded, and that is what an investigation is actually about.
Navigation is not accessOpening the plan page is not a read of anybody’s trail. A log full of page views is a log nobody reads, which would make the whole thing decorative.
An attempt is not an accessA refused request is not recorded here. Failed probes belong in your own security telemetry, not in the same list as real reads.
The filter is keptWhat was asked for, not just that something was. "Exported one order" and "exported the whole of last year" are different events and should not look identical.

Kept separately from your own events, so it never inflates what you are metered on and never turns "what did our application do" into a question you have to filter our noise out of. Reads through the query API are recorded against the API key that made them.

Narrowing an account

A dashboard account can be limited to particular projects, particular resource types, or both. Everything it is not allowed to read is simply not there: not in the log, not on the overview, not in the dropdowns, not on a pasted permalink, and not in an export.

This is the feature that decides whether a larger company centralises its change history at all. "Everyone with an account sees everything" is fine for four people; at forty it means the support team can read payroll, and the honest answer becomes to keep the history scattered across the systems that produced it. Narrowing the account is what makes one trail the safer option rather than the riskier one.
The scope is on the sessionIt is decided when the person signs in and never read from a request. A limit the browser can influence is not a limit.
Asking for more is refused, not widenedA filter naming a resource type outside the scope returns nothing and says so. Quietly rewriting it to something allowed would answer a question nobody asked; quietly dropping it would widen the read.
It holds on every routeThe log, the permalink, the overview tiles, the report builder and the export are all the same boundary. A limit that holds on the list and not on the export is a filter, and a filter is not a boundary.
The dropdowns shrink tooA type the account may not read is not offered. The list of names is itself information: hr.payslip in a dropdown tells you the company runs payroll through this system.
Empty means everythingAn account with no limits set behaves exactly as every account did before this existed, so turning it on changes nothing for the people who do not need it.
reldavi viewer --email dispatch@acme.test --types shop.order,shop.shipment

It takes effect the next time they sign in, because the scope is written onto the session. Narrowed accounts still appear in Who read the trail exactly like any other: a smaller window is still a window, and the reads through it are still reads.

Embedding the trail in your product

Your customers can read their own audit trail inside your product, without you building a second dashboard and without them having an account with us. Mint a scoped token on your server and put it in an iframe.

dotnet add package Reldavi.Embedding

// On your server, never in the browser.
var token = EmbedToken.Issue(
    apiKey,
    new EmbedScope(tenantSlug, ResourceType: "shop.order", ResourceId: order.Id.ToString()),
    TimeSpan.FromMinutes(5));
<iframe src="https://app.reldavi.com/embed?t=@token" title="Activity"></iframe>
The token is the entire boundaryIt names one tenant and, optionally, one resource type, one record or one actor. A scope narrows; it can never be talked into widening.
No secret is exchangedThe signature is HMAC-SHA256 keyed on the SHA-256 of your API key’s secret segment — which is exactly what we already store for that key. You mint from the key in your hand; we verify from the digest in our database.
Fifteen minutes at most, five by defaultThe token rides in a URL, so it ends up in browser history, Referer headers and whatever logs sit in between. A leaked one should be worthless by the time anybody finds it.
Framing is opt-in per originframe-ancestors is built from the origins you list in Settings → Embedded viewer. List nothing and the viewer refuses every frame, including yours.
Revocation worksRevoke the key and every token it ever signed stops immediately.
Mint on your server. A token minted in front of the user is a token the user can mint, and then the scope stops being a boundary. scope.IsNarrowed is there to check before you hand one out.

Single sign-on

Any OpenID Connect provider — Entra ID, Okta, Google Workspace, Keycloak. Set four values and a second button appears on the sign-in page, labelled with whatever your company actually calls that login.

SingleSignOn__Enabled=true
SingleSignOn__Authority=https://login.microsoftonline.com/<tenant>/v2.0
SingleSignOn__ClientId=<client id>
SingleSignOn__ClientSecret=<client secret>
SingleSignOn__ButtonLabel=Sign in with Entra ID
Register the redirect URIshttps://your-dashboard/signin-sso and https://your-dashboard/signout-sso. The authorisation code flow with PKCE; the implicit flow is not offered, because it puts a token in a URL and URLs end up in browser history.
The provider authenticates, we authoriseWhat comes back is one thing: an email address that has proved it is itself. Which tenant that person belongs to, what role they hold and what they may read all come from our database, never from the token.
Accounts are not created on the way inSomebody who signs in successfully and has no account here is refused. The provider cannot know which tenant a stranger belongs to, and a wrong guess would hand somebody another company's audit history — so anybody with a mailbox in that directory, including the contractor nobody remembered to remove, would otherwise get in.
Deactivation works through every doorAn account disabled here is refused at the SSO callback too, not only at the password form.

Passwords keep working alongside it, which matters on the day the provider is the thing that is down. Turn it off by removing the configuration; nothing else changes.

What to read next

Stuck on something?

The schema, the SDK sources and the full architecture notes are open. If something is unclear, that is a bug in our documentation. Get in touch.