Retention management
Control how long your media files are stored in Mappa. This guide covers retention policies, locking files, and optimizing storage costs.
How retention works
Mappa uses an inactivity-based retention policy:
- Initial upload:
lastUsedAtis set to upload time - Report generation:
lastUsedAtis updated each time you create a report - Inactivity period: Files are deleted after 6 months of inactivity
- Grace period: Soft-deleted files are kept for a short period before permanent deletion
Retention clock
Upload → lastUsedAt = now
↓
(create report)
↓
lastUsedAt = now (clock resets)
↓
(6 months pass)
↓
Auto-delete
Key insight: Actively used files are never deleted automatically.
Checking retention status
Every file response includes retention information:
const file = await mappa.files.get(mediaId);
console.log(file.retention);
// {
// expiresAt: "2026-07-17T12:00:00.000Z",
// daysRemaining: 181,
// locked: false
// }
Retention fields
| Field | Type | Description |
|---|---|---|
expiresAt | string | null | When file will be auto-deleted (null if locked) |
daysRemaining | number | null | Days until deletion (null if locked) |
locked | boolean | Whether file is protected from auto-deletion |
Locking files
Locked files are retained indefinitely until manually deleted.
When to lock files
✅ Training data:
// Lock reference recordings used for model training
await mappa.files.setRetentionLock(mediaId, true);
✅ Legal/compliance requirements:
// Lock files that must be retained for legal reasons
await mappa.files.setRetentionLock(regulatoryMediaId, true);
✅ Quality assurance samples:
// Lock QA test files
for (const sample of qaSamples) {
await mappa.files.setRetentionLock(sample.mediaId, true);
}
Locking API
/v1/files/:mediaId/retentionawait mappa.files.setRetentionLock(mediaId, true);
// File will never be auto-deleted
const file = await mappa.files.get(mediaId);
console.log(file.retention);
// {
// expiresAt: null,
// daysRemaining: null,
// locked: true
// }
await mappa.files.setRetentionLock(mediaId, false);
// File resumes normal 6-month retention from lastUsedAt
const file = await mappa.files.get(mediaId);
console.log(file.retention);
// {
// expiresAt: "2026-07-17T12:00:00.000Z",
// daysRemaining: 181,
// locked: false
// }
Retention strategies
Strategy 1: lock important files
async function protectImportantFiles(mediaIds: string[]) {
for (const mediaId of mediaIds) {
await mappa.files.setRetentionLock(mediaId, true);
console.log(`✓ Locked ${mediaId}`);
}
}
// Lock all QA samples
await protectImportantFiles([
"media_qa_sample_1",
"media_qa_sample_2",
"media_qa_sample_3",
]);
Strategy 2: active usage keeps files
// No need to lock if you generate reports regularly
async function monthlyAnalysis(candidates: Candidate[]) {
for (const candidate of candidates) {
// This updates lastUsedAt, resetting the retention clock
await mappa.reports.createJob({
media: { mediaId: candidate.latestInterview },
output: { template: "hiring_report" },
target: { strategy: "entity_id", entityId: candidate.entityId },
});
}
}
// Run monthly - files never expire due to inactivity
Strategy 3: selective locking
async function manageRetention(file: MediaFile) {
const monthsSinceUpload = getMonthsSince(file.createdAt);
if (file.category === "training_data") {
// Always lock training data
await mappa.files.setRetentionLock(file.mediaId, true);
} else if (monthsSinceUpload > 3 && file.reportCount === 0) {
// Delete unused files after 3 months
await mappa.files.delete(file.mediaId);
} else if (file.reportCount > 10) {
// Lock frequently-used files
await mappa.files.setRetentionLock(file.mediaId, true);
}
// Otherwise, let normal retention policy handle it
}
Monitoring expiring files
Get notified before files expire:
async function checkExpiringFiles(warningDays = 30) {
const files = await mappa.files.list({ limit: 100 });
const expiringSoon = files.files.filter(file => {
if (file.retention.locked) return false;
return file.retention.daysRemaining !== null &&
file.retention.daysRemaining < warningDays;
});
if (expiringSoon.length > 0) {
console.warn(`⚠️ ${expiringSoon.length} files expiring in <${warningDays} days:`);
for (const file of expiringSoon) {
console.warn(` - ${file.mediaId}: ${file.retention.daysRemaining} days`);
}
// Send alert
await alertService.send({
type: "warning",
message: `${expiringSoon.length} media files expiring soon`,
files: expiringSoon,
});
}
}
// Run daily
setInterval(checkExpiringFiles, 24 * 60 * 60 * 1000);
Bulk retention management
Lock all training data
async function lockTrainingDataset(tag: string) {
const files = await getAllFilesWithTag(tag);
console.log(`Locking ${files.length} training files...`);
for (const file of files) {
await mappa.files.setRetentionLock(file.mediaId, true);
}
console.log(`✓ Locked ${files.length} files`);
}
await lockTrainingDataset("training_dataset_v2");
Unlock temporary files
async function unlockTemporaryFiles() {
const files = await mappa.files.list({ limit: 100 });
const locked = files.files.filter(f => f.retention.locked);
for (const file of locked) {
// Check if file is still needed
const reports = await database.reports.countByMediaId(file.mediaId);
if (reports === 0 && isOlderThan(file.createdAt, 90)) {
// No reports generated in 90 days - unlock
await mappa.files.setRetentionLock(file.mediaId, false);
console.log(`Unlocked ${file.mediaId}`);
}
}
}
Storage cost optimization
Storage pricing
Locked files incur storage costs:
- Rate: 15 credits per GB per month
- Billing: Prorated daily
- Rounding: Rounded up to nearest integer (no fractional credits)
Daily cost formula:
daily_cost = ceil(size_in_gb × 15 / 30)
Examples:
| Storage | Calculation | Daily Cost |
|---|---|---|
| 500 MB (0.5 GB) | ceil(0.5 × 15 / 30) = ceil(0.25) | 1 credit |
| 2 GB | ceil(2 × 15 / 30) = ceil(1) | 1 credit |
| 5 GB | ceil(5 × 15 / 30) = ceil(2.5) | 3 credits |
| 10 GB | ceil(10 × 15 / 30) = ceil(5) | 5 credits |
Only lock files you truly need to retain. Unlocked files are auto-deleted after 6 months of inactivity at no storage cost. See Pricing for current cost assumptions.
Audit locked files
async function auditLockedFiles() {
const allFiles = await getAllFiles();
const locked = allFiles.filter(f => f.retention.locked);
console.log(`\n=== Locked Files Audit ===`);
console.log(`Total files: ${allFiles.length}`);
console.log(`Locked files: ${locked.length} (${(locked.length / allFiles.length * 100).toFixed(1)}%)`);
// Calculate storage cost
const totalSizeGB = locked.reduce((sum, f) => sum + f.sizeBytes, 0) / (1024 ** 3);
console.log(`Total locked storage: ${totalSizeGB.toFixed(2)} GB`);
// Group by purpose
const byPurpose = groupBy(locked, f => f.metadata?.purpose || "unknown");
for (const [purpose, files] of Object.entries(byPurpose)) {
console.log(` ${purpose}: ${files.length} files`);
}
}
// Run monthly
await auditLockedFiles();
Cleanup unused files
async function cleanupUnusedFiles(inactiveDays = 90) {
const files = await mappa.files.list({ limit: 100 });
for (const file of files.files) {
if (file.retention.locked) continue; // Skip locked files
const daysSinceUsed = getDaysSince(file.lastUsedAt);
if (daysSinceUsed > inactiveDays) {
console.log(`Deleting inactive file: ${file.mediaId} (${daysSinceUsed} days)`);
await mappa.files.delete(file.mediaId);
}
}
}
// Run weekly
await cleanupUnusedFiles(90);
Best practices
✅ Do
- Lock files you'll reference repeatedly
- Monitor
daysRemainingfor critical files - Unlock files you no longer need
- Run periodic audits of locked files
- Document why files are locked (use metadata)
❌ Don't
- Lock everything (storage costs 15 credits/GB/month)
- Forget about locked files
- Delete files with active jobs
- Lock files "just in case" without reason
Enterprise considerations
For high-volume production use:
-
Implement retention policies:
- Lock: Training data, QA samples, legal compliance
- 6-month: Active projects
- Manual delete: Test files, one-off analyses
-
Automate monitoring:
- Daily expiration checks
- Weekly locked file audits
- Monthly storage cost reports
-
Tag files appropriately:
// Use metadata to track retention reasons
await database.mediaFiles.update(mediaId, {
metadata: {
purpose: "training_data",
lockedReason: "QA reference sample v2.1",
lockedBy: "admin@company.com",
lockedAt: new Date().toISOString(),
}
});
Next steps
- Files API - Full retention API reference
- Credit Optimization - Minimize costs
- Entity Management - Track speakers across files