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 nameretention-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.
