CPRA amended CCPA to require, at § 1798.100(a)(3), that businesses disclose retention periods for each category of personal information — or the criteria used to determine them. A privacy policy that says "we retain your information for as long as necessary" satisfies neither the letter nor the spirit of this requirement. Beyond compliance, undisclosed retention creates a data-breach surface: data that should have been deleted years ago is still in the database when a breach occurs. Disclosed retention periods only close the compliance gap if automated enforcement — cron jobs, database lifecycle rules — actually delete or anonymize the data when periods expire. Policy and code must match.
Low because retention disclosure violations are unlikely to trigger priority enforcement on their own, but unmatched retention periods between policy and code are a data-minimization failure that amplifies breach impact.
Add a Data Retention section to your privacy policy using CCPA-defined category names and specific periods, then implement automated deletion jobs that enforce those periods.
## Data Retention (privacy policy section — use CCPA category names)
| CCPA Category | Retention Period |
|-----------------------------|------------------------------------------------|
| Identifiers (account data) | Until deletion request + 30-day grace period |
| Internet/network activity | 26 months from collection |
| Commercial information | 7 years (US tax requirements) |
| Geolocation data | 90 days |
| Server/access logs | 90 days |
// app/api/cron/retention-cleanup/route.ts
export async function GET() {
const analyticsCutoff = new Date()
analyticsCutoff.setMonth(analyticsCutoff.getMonth() - 26)
await db.analyticsEvent.deleteMany({ where: { createdAt: { lt: analyticsCutoff } } })
const logCutoff = new Date()
logCutoff.setDate(logCutoff.getDate() - 90)
await db.accessLog.deleteMany({ where: { createdAt: { lt: logCutoff } } })
return Response.json({ ok: true })
}
Register this route in vercel.json as a cron job running daily.
ID: ccpa-readiness.data-handling.retention-limits
Severity: low
What to look for: CPRA requires businesses to disclose retention periods for each category of personal information they collect. Check the privacy policy for a data retention section that specifies how long each category of PI is retained, or the criteria used to determine retention periods. Look for matching automated enforcement: cron jobs, database lifecycle rules, or scheduled functions that delete or anonymize data when retention periods expire. Cross-reference the disclosed retention periods with what is actually implemented in code. Common issues: policy says "we retain data for 2 years" but no automated deletion exists; or no retention periods disclosed at all. Count every PI category in the privacy policy and enumerate whether each has a stated retention period. Report: X of Y categories have disclosed retention periods.
Pass criteria: Privacy policy discloses retention periods (or the criteria for determining them) for each category of PI collected. Automated processes enforce those retention periods. Report the count of PI categories with disclosed retention periods even on pass. At least 1 implementation must be confirmed.
Fail criteria: No retention period disclosures in the privacy policy. Retention periods are disclosed but no automated enforcement exists. Disclosed periods do not match what is implemented in code.
Skip (N/A) when: Same CCPA threshold analysis — document if skipping.
Detail on fail: Example: "Privacy policy states 'we retain your information for as long as necessary' without specific periods. No automated deletion cron jobs found in codebase." or "Policy states email retained for 2 years after account deletion but no automated deletion confirmed.".
Remediation: Disclose retention periods and implement automated enforcement:
## Data Retention (add to privacy policy — use CCPA category names)
| Category of PI | Retention Period |
|----------------------------|--------------------------------------------------------|
| Identifiers (account data) | Until account deletion, then 30-day grace period |
| Internet/network activity | 26 months from collection |
| Commercial information | 7 years (US tax/accounting requirements) |
| Geolocation data | 90 days |
| Server/access logs | 90 days |
| Inferences | Until account deletion |
// app/api/cron/retention-cleanup/route.ts
export async function GET() {
const now = new Date()
// Delete analytics events older than 26 months
const analyticsCutoff = new Date(now)
analyticsCutoff.setMonth(analyticsCutoff.getMonth() - 26)
await db.analyticsEvent.deleteMany({ where: { createdAt: { lt: analyticsCutoff } } })
// Delete access logs older than 90 days
const logCutoff = new Date(now)
logCutoff.setDate(logCutoff.getDate() - 90)
await db.accessLog.deleteMany({ where: { createdAt: { lt: logCutoff } } })
return Response.json({ ok: true, ranAt: now.toISOString() })
}