Fintech Core keeps two separate records of activity. They look similar and are not interchangeable, and the difference is the point.
Never treat a request log as evidence of a decision. It is an operational aid: it can drop rows under pressure and it is deleted on a schedule. Anything that must be defended later — a KYC approval, a key revocation, a settings change — is in the audit trail.

Audit events

Written in the same transaction as the change they describe. An approval that left no trace is worse than an approval that did not happen, so the audit write failing fails the action. Actions are dotted and past tense — kyc.review.approved, api_key.revoked, settings.updated. GET /api/v1/admin/events/types returns the live vocabulary, so a filter cannot drift from what is actually written. Each event records the actor (Islem <admin:…>, stable across renames), the entity it acted on, the originating IP and user agent, and a small metadata object. The metadata holds decisions and identifiers only — a name or a document number there would turn the audit log into a second, browsable copy of the data it audits.

Request logs

Every API request, minus health probes and static assets. Each row carries the method, path, status, latency, IP, user agent, the API key prefix, and:
  • actor — who made the call. An admin JWT carries its subject’s id and name in its claims, so this is resolved in-process with no database hit. An API key contributes only its non-secret prefix; the owning business is resolved by joining on that prefix at read time, which keeps a lookup off the hot path.
  • requestBody / responseBody — redacted, and often deliberately absent.

What happens to bodies

Capture is defended twice, and both defences are conservative on purpose.
  1. A path deny-list. Endpoints whose purpose is releasing applicant identity — /kyc/applications/{id} and its report routes, /compliance/screen, /kyc/webhook — store no body at all, in either direction. Redaction is a filter, and a filter is the wrong tool when the entire payload is the sensitive thing.
  2. Key redaction everywhere else. Any field whose name matches a sensitive key is replaced with [redacted] at every depth, including inside arrays. That covers applicant identity (surname, document number, birth date, contact details), credentials (passwords, API keys, tokens, signatures), and capability URLs — a KYC linkUrl carries a session token that lets its holder open that applicant’s capture page, so it is a bearer credential that happens to look like a link.
A body that cannot be parsed as JSON is dropped rather than stored raw, because an unparsed body cannot be redacted. Bodies over 8 KB are replaced with a size note, and requests whose Content-Length is missing or above 32 KB are not buffered at all — they stream straight to the handler and are simply not logged.
An empty body in the dashboard is normal, not a bug. The drawer says which of the four reasons applies: identity-bearing path, empty payload, capture switched off, or unparseable. “No body” and “body withheld” lead an operator to different conclusions.

Retention

Retention is an operator setting, not a redeploy. It lives in platform_settings and is edited under Settings → Platform: A background worker wakes every five minutes, re-reads these, and prunes once the interval has elapsed. It ticks faster than it prunes on purpose: the obvious sleep(interval); prune() loop means an operator who shortens the interval from a week to an hour waits up to a week for that to take effect, because the loop is already asleep on the old value. The lower bound of 1 day is deliberate. A retention of 0 would mean “delete everything on the next tick” — a destructive action nobody should be able to take by mistyping a number in a form.

Clearing by hand

DELETE /api/v1/admin/request-logs deletes immediately, optionally scoped with ?olderThanDays=N. It requires settings:manage — deleting operational history is a settings-level action, not a read. The deletion is recorded in the audit trail before it runs, in the same transaction. The record of the clear therefore survives the clear, which is the whole reason the two tables have different retention.

Permissions

settings:manage belongs to full admins only. It does not follow developers:manage around, because the ability to shorten retention to a day is the ability to make an incident un-investigable.