Skip to content

Audit Logging

pynteracta can log every outgoing API call — including request/response headers and (optionally) bodies — to the console and/or a rotating JSON-lines file.

Audit logging is off by default. All audit output is redacted; raw output requires an explicit opt-in with a loud warning.


What is captured

Always (when audit is on) Opt-in (audit_log_bodies)
HTTP method Request body (JSON payload)
Redacted URL Response body (parsed JSON / truncated text)
Redacted request headers —
Redacted response headers —
Status code, duration, X-Request-Id —

Response bodies larger than 64 KB are truncated with a ...[truncated] marker.


Enabling via config file

[profiles.default]
base_url = "https://your-tenant.interacta.cloud"
audit_log = true
audit_log_file = "/var/log/pynteracta/audit.log"
audit_log_bodies = true          # opt-in bodies
audit_log_max_bytes = 10000000   # 10 MB per file
audit_log_backups = 5            # keep 5 rotated files

Enabling via environment variables

Variable Default Description
PYNTERACTA_AUDIT_LOG false Enable audit logging
PYNTERACTA_AUDIT_LOG_FILE (none) Path to rotating log file; setting this implies AUDIT_LOG=true
PYNTERACTA_AUDIT_LOG_BODIES false Capture request/response bodies
PYNTERACTA_AUDIT_LOG_RAW false Bypass redaction (unsafe, see below)
PYNTERACTA_AUDIT_LOG_MAX_BYTES 10000000 Max bytes per log file before rotation
PYNTERACTA_AUDIT_LOG_BACKUPS 5 Number of rotated backup files to keep

Enabling via CLI flags

pynteracta --audit-log \
           --audit-log-file /tmp/audit.log \
           --audit-bodies \
           users list

Setting --audit-log-file implies --audit-log automatically.


Log format

Each entry is a single JSON object on its own line (JSON-lines / NDJSON), written via Python's RotatingFileHandler. Example:

{"event": "audit.request", "log_level": "debug", "logger": "pynteracta.audit", "timestamp": "2026-06-02T10:00:00Z", "method": "GET", "url": "https://tenant.interacta.cloud/.../core/auth/current-user-data", "headers": {"User-Agent": "pynteracta/0.9.1 ...", "Authorization": "***REDACTED***"}, "body": null}
{"event": "audit.response", "log_level": "debug", "logger": "pynteracta.audit", "timestamp": "2026-06-02T10:00:00Z", "status": 200, "url": "...", "headers": {"content-type": "application/json;charset=utf-8", "set-cookie": "interacta_auth_refresh_token=***REDACTED***; Secure; HttpOnly; SameSite=Strict; Path=/portal/api/core/auth/refresh-token/; Expires=Thu, 02-Jul-2026 16:06:04 GMT"}, "duration_ms": 134.2, "request_id": "abc-123", "body": {"userId": 42, ...}}

Redaction guarantees

  • The Authorization header is always replaced with ***REDACTED***.
  • Headers redacted by name (since 0.9.5): Authorization, Proxy-Authorization, and any header whose name contains token, secret, password or api-key (case-insensitive, e.g. X-Api-Key, X-Auth-Token) lose their whole value, whatever its shape.
  • Cookies (since 0.9.5): in Cookie and Set-Cookie every cookie keeps its name and attributes (Secure, HttpOnly, SameSite, Path, Expires, …) and loses its value, which becomes ***REDACTED*** — see the set-cookie example above. A cookie header that cannot be parsed with certainty (no =, quoted values) is redacted as a whole: when in doubt, redact.
  • Any value matching a JWT pattern (eyJ…) is replaced — in headers, bodies, and string fields.
  • Body keys matching token, password, secret, privatekey, assertion, jwt, signature or policy (case-insensitive) at any nesting depth are replaced: signature and policy are the fields of the signed upload policy returned by upload-new-attachment.
  • Optimistic-concurrency tokens stay readable (since 0.13.1): the keys occToken, nextOccToken, screenOccToken and nextScreenOccToken (and their snake_case spellings, such as occ_token) are not redacted, although they contain token. They are version counters of the entity, not credentials, and with audit_log_bodies they let you tell which token a write read and which one it sent. The exception matches these exact names only (case-insensitive): any other key containing token, such as accessToken, nextPageToken or userOccToken, is still replaced.
  • Signed URLs: in any URL, the value of a query parameter whose name contains signature, token, secret, password or key is replaced, while its name and the other parameters stay: …?GoogleAccessId=…&Expires=1791633600&Signature=***REDACTED***. This covers the temporary download and preview links of attachments, in URLs, bodies and exception messages.
  • Uploaded files never reach the logs, the audit log or the hooks: the storage request is recorded with its URL and status only, with no body, even with audit_log_bodies; it never carries the Interacta token.
  • Redaction runs on both the console and the file handler independently; neither channel ever receives a raw token.
  • Response payloads are redacted inside the transport, before either observer sees them. This matters for the hooks channel: a ClientHooks.on_response implementation receives ResponseInfo directly, and since the library never configures logging on your behalf, no logging setup could have scrubbed it. Since v0.9.3 the guarantee holds for both channels without any configuration on your part.

--audit-raw: unsafe redaction bypass

pynteracta --audit-log --audit-raw users list

When --audit-raw is set, redaction is disabled on the audit channel (the pynteracta.audit logger). A one-time WARNING is emitted:

audit.redaction_disabled: --audit-raw is active — raw tokens and sensitive data MAY appear in audit output. Never use this in production.

Never use --audit-raw in production. It will expose bearer tokens and service-account keys in plain text.

--audit-raw affects only the audit logging channel. It does not bypass the transport-level redaction described above, so ResponseInfo handed to ClientHooks.on_response stays redacted either way.


Using the audit logger from library code

When you call setup_default_logging(audit=True, ...), the dedicated pynteracta.audit logger is wired up independently of the main pynteracta logger. Audit events (audit.request, audit.response) are emitted at DEBUG level and do not propagate to the root pynteracta logger (no double-printing).

from pathlib import Path

from pynteracta.logging import setup_default_logging

setup_default_logging(
    level="INFO",
    audit=True,
    audit_file=Path("/tmp/audit.log"),
    audit_bodies=True,
)