Skip to main content

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: lastUsedAt is set to upload time
  • Report generation: lastUsedAt is 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

FieldTypeDescription
expiresAtstring | nullWhen file will be auto-deleted (null if locked)
daysRemainingnumber | nullDays until deletion (null if locked)
lockedbooleanWhether 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

PATCH/v1/files/:mediaId/retention
Lock a File
await 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
// }
Unlock a File
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:

expiration-monitor.ts
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:

StorageCalculationDaily Cost
500 MB (0.5 GB)ceil(0.5 × 15 / 30) = ceil(0.25)1 credit
2 GBceil(2 × 15 / 30) = ceil(1)1 credit
5 GBceil(5 × 15 / 30) = ceil(2.5)3 credits
10 GBceil(10 × 15 / 30) = ceil(5)5 credits
Minimize Storage Costs

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 daysRemaining for 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:

  1. Implement retention policies:

    • Lock: Training data, QA samples, legal compliance
    • 6-month: Active projects
    • Manual delete: Test files, one-off analyses
  2. Automate monitoring:

    • Daily expiration checks
    • Weekly locked file audits
    • Monthly storage cost reports
  3. 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