Trust Center · Compliance

Data retention and deletion

Enforced retention schedule, the daily sweep, the data-subject request route, and logging hygiene.

Back to Trust Center

Source: docs/compliance/retention.md

Data retention and deletion — AXOS-operated services

Scope. This document covers personal data held by ScotiTech Solutions Limited for the AXOS marketing site, the hosted demo environment, and the registration / OTP flow behind them. It does not cover self-hosted AXOS deployments: there the customer is sole controller and sets their own retention.

Authoritative source. The windows below are declared once in apps/api/src/retention/retention.policy.ts and enforced by RetentionService. Change that file and update this document, the privacy policy, and the DPA in the same commit. A unit test asserts the values so drift fails CI.

Retention schedule

Data Where held Window What happens at expiry
One-time passcodes (hashed) email_verifications 24 hours after expiry or use Row deleted
Unverified registrations registrations where emailVerified = false 30 days from submission Row deleted
Rejected registrations registrations where status = rejected 90 days from last update Row deleted
Active / pending registrations registrations 24 months from last meaningful update Row deleted
Hosted-demo credentials registrations.demoCredentialsSent 14 days from issue demoExpiredAt stamped; access revoked on the demo host
Operational request logs Reverse proxy / app logs on ScotiTech hosts 90 days Log rotation

OTP codes are stored only as a SHA-256 hash; the plaintext is never persisted and never logged.

How the sweep runs

  • RetentionService.scheduledSweep() runs daily at 03:00 UTC via @nestjs/schedule (job name retention-sweep).
  • The sweep is idempotent — it only deletes rows already past their window, so a missed run is caught up on the next one.
  • Errors are logged and swallowed so one bad night never stops the cron.
  • Operators can run it on demand: pnpm --filter api retention:sweep.

Verifying it is registered (post-deploy check)

# 1. Confirm the job is scheduled (look for "retention-sweep" in startup logs)
docker logs axos-api 2>&1 | grep -i "retention"

# 2. Run once and read the counts
docker exec axos-api node -e "require('./dist/retention/retention.cli.js')"

A healthy run prints JSON with ranAt and five integer counters.

Data-subject requests

Export or erasure is performed by an operator holding ADMIN_API_KEY:

curl -sS -X POST https://axosapi.scotitech.com/registrations/data-subject-request \
  -H "content-type: application/json" \
  -H "x-axos-admin-key: $ADMIN_API_KEY" \
  -d '{"email":"person@example.com","action":"erase"}'

action is export or erase. Erasure is immediate for the registration and verification tables. Bounded operational logs and host backups roll off on the schedule above. The request itself is logged without the subject's identity.

Logging hygiene

No full email address, OTP code, password, or demo credential is written to application logs. Correlation uses emailRef() (a short, non-reversible hash) or maskEmail() (j***@example.com). A unit test exercises the OTP and DSR paths and asserts the raw address never appears in captured log output.

Review cadence

Reviewed with every change to retention.policy.ts, and at least annually. Last reviewed: 2026-08-21.