Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Handle data privacy, retention, and compliance for Gamma integrations. Use when implementing GDPR compliance, data retention policies, or managing user data within Gamma workflows. Trigger with phrases like "gamma data", "gamma privacy", "gamma GDPR", "gamma data retention", "gamma compliance".
.claude/skills/jeremylongshore-gamma-data-handling/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 49% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 27% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 26% | 0% |
Data handling, privacy controls, and compliance for Gamma API integrations. Gamma processes user-submitted content through AI to generate presentations -- understand what data flows where and how to handle PII, retention, and GDPR requirements.
gamma-install-auth setupUser Input (content, prompts)
│
▼
┌──────────────┐
│ Your App │ ← PII may be in content (names, company data)
│ (API key) │
└──────┬───────┘
│ POST /v1.0/generations
▼
┌──────────────┐
│ Gamma API │ ← Content processed by AI
│ (gamma.app) │ ← Images generated
└──────┬───────┘
│ gammaUrl + exportUrl
▼
┌──────────────┐
│ Generated │ ← Presentation stored in Gamma workspace
│ Content │ ← Export files (PDF/PPTX/PNG) temporary
└──────────────┘| Data Type | Classification | Where Stored | Retention | |-----------|---------------|--------------|-----------| | API key | Secret | Your env vars | Active use only | | Content/prompts | May contain PII | Gamma servers (during generation) | Gamma's policy | | Generated gammas | User data | Gamma workspace | User-controlled | | Export files (PDF/PPTX) | User data | Temporary URLs | Download promptly, URLs expire | | User prompts in logs | PII risk | Your infrastructure | Your policy (sanitize!) | | Credit usage | Billing data | Gamma | Per Gamma ToS |
typescript// src/gamma/sanitize.ts // Remove PII from content before sending to Gamma if not needed interface SanitizeOptions { removeEmails: boolean; removePhones: boolean; maskNames: boolean; } function sanitizeContent(content: string, opts: SanitizeOptions): string { let sanitized = content; if (opts.removeEmails) { sanitized = sanitized.replace(/[\w.-]+@[\w.-]+\.\w+/g, "[email]"); } if (opts.removePhones) { sanitized = sanitized.replace(/\+?[\d\s()-]{10,}/g, "[phone]"); } if (opts.maskNames) { // Only mask if you have a list of known names // Generic regex would be too aggressive } return sanitized; } // Usage: sanitize before generation const safeContent = sanitizeContent(userContent, { removeEmails: true, removePhones: true, maskNames: false, }); await gamma.generate({ content: safeContent, outputFormat: "presentation", });
typescript// src/gamma/logging.ts // Never log raw content or API keys function logGeneration(request: any, result: any) { console.log(JSON.stringify({ event: "gamma_generation", timestamp: new Date().toISOString(), generationId: result.generationId, outputFormat: request.outputFormat, contentLength: request.content?.length, // NEVER log: content (may have PII), apiKey status: result.status, creditsUsed: result.creditsUsed, })); }
typescript// src/gamma/exports.ts // Export URLs are temporary — download and store securely import { writeFile } from "node:fs/promises"; import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; async function archiveExport( exportUrl: string, metadata: { generationId: string; userId: string } ) { // Download immediately — URLs expire const res = await fetch(exportUrl); if (!res.ok) throw new Error(`Export download failed: ${res.status}`); const buffer = Buffer.from(await res.arrayBuffer()); // Store with encryption const s3 = new S3Client({ region: "us-east-1" }); const key = `gamma-exports/${metadata.userId}/${metadata.generationId}.pdf`; await s3.send(new PutObjectCommand({ Bucket: process.env.EXPORTS_BUCKET!, Key: key, Body: buffer, ContentType: "application/pdf", ServerSideEncryption: "aws:kms", Metadata: { generationId: metadata.generationId, archivedAt: new Date().toISOString(), }, })); console.log(`Archived: s3://${process.env.EXPORTS_BUCKET}/${key}`); }
typescript// src/gamma/retention.ts interface RetentionPolicy { exportMaxDays: number; // Delete local export copies logRetentionDays: number; // Anonymize generation logs promptRetentionDays: number; // Delete stored prompts } const POLICY: RetentionPolicy = { exportMaxDays: 90, // Keep exports 90 days logRetentionDays: 30, // Anonymize logs after 30 days promptRetentionDays: 7, // Delete prompts after 7 days }; async function enforceRetention() { const cutoff = new Date(); // Delete old exports from S3 cutoff.setDate(cutoff.getDate() - POLICY.exportMaxDays); await deleteOldExports(cutoff); // Anonymize old logs cutoff.setDate(cutoff.getDate() + POLICY.exportMaxDays - POLICY.logRetentionDays); await anonymizeLogs(cutoff); // Delete stored prompts cutoff.setDate(cutoff.getDate() + POLICY.logRetentionDays - POLICY.promptRetentionDays); await deletePrompts(cutoff); }
typescript// Handle data subject access/erasure requests async function handleGdprRequest( type: "access" | "erasure", userId: string ) { if (type === "access") { // Return all data we store about this user return { generations: await db.generations.findMany({ where: { userId } }), exports: await listS3Objects(`gamma-exports/${userId}/`), // Note: data stored IN Gamma's workspace is Gamma's responsibility // Direct user to gamma.app to access/delete their workspace data }; } if (type === "erasure") { // Delete from our systems await db.generations.deleteMany({ where: { userId } }); await deleteS3Prefix(`gamma-exports/${userId}/`); // Instruct user to delete Gamma workspace data at gamma.app return { deleted: true, note: "Delete Gamma workspace data at gamma.app" }; } }
| Error | Cause | Solution | |-------|-------|----------| | Export URL expired | Downloaded too late | Download immediately on generation completion | | PII in logs | Missing sanitization | Add log sanitization middleware | | Retention job failed | Scheduler stopped | Monitor cron job health | | GDPR request incomplete | Gamma workspace not addressed | Direct user to gamma.app for workspace data |
Proceed to gamma-enterprise-rbac for access control.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 23,420 | 21,103 | -10% | 1 | 1 | 0% | 3,579 | 5,379 | +50% | 0 | 0 | — |
case-02 | fail→pass | 23,837 | 26,719 | +12% | 1 | 1 | 0% | 4,378 | 6,528 | +49% | 0 | 0 | — |
case-03 | fail→pass | 23,737 | 11,958 | -50% | 1 | 1 | 0% | 3,397 | 4,323 | +27% | 0 | 0 | — |
case-04 | fail→fail | 16,598 | 10,928 | -34% | 1 | 1 | 0% | 3,101 | 3,973 | +28% | 0 | 0 | — |
case-05 | pass→pass | 17,986 | 17,260 | -4% | 1 | 1 | 0% | 3,161 | 4,134 | +31% | 0 | 0 | — |
case-06 | pass→pass | 18,358 | 18,834 | +3% | 1 | 1 | 0% | 2,248 | 4,457 | +98% | 0 | 0 | — |
case-07 | fail→pass | 11,819 | 8,829 | -25% | 1 | 1 | 0% | 1,898 | 2,687 | +42% | 0 | 0 | — |
case-08 | fail→pass | 11,112 | 3,605 | -68% | 1 | 1 | 0% | 1,773 | 2,614 | +47% | 0 | 0 | — |
case-09 | fail→pass | 11,307 | 2,551 | -77% | 1 | 1 | 0% | 1,931 | 2,425 | +26% | 0 | 0 | — |
case-10 | fail→pass | 11,835 | 11,267 | -5% | 1 | 1 | 0% | 1,890 | 2,929 | +55% | 0 | 0 | — |
case-11 | fail→pass | 20,746 | 19,305 | -7% | 1 | 1 | 0% | 3,739 | 4,814 | +29% | 0 | 0 | — |
case-12 | fail→fail | 10,833 | 7,329 | -32% | 1 | 1 | 0% | 2,113 | 3,516 | +66% | 0 | 0 | — |
case-13 | fail→fail | 11,282 | 12,766 | +13% | 1 | 1 | 0% | 2,304 | 3,366 | +46% | 0 | 0 | — |
case-14 | fail→pass | 13,204 | 2,605 | -80% | 1 | 1 | 0% | 1,527 | 2,476 | +62% | 0 | 0 | — |
case-15 | pass→pass | 10,904 | 6,937 | -36% | 1 | 1 | 0% | 2,030 | 3,234 | +59% | 0 | 0 | — |
case-16 | pass→pass | 19,784 | 14,827 | -25% | 1 | 1 | 0% | 2,706 | 4,803 | +77% | 0 | 0 | — |
case-17 | fail→pass | 13,459 | 8,159 | -39% | 1 | 1 | 0% | 2,493 | 3,499 | +40% | 0 | 0 | — |
case-18 | fail→pass | 16,296 | 1,980 | -88% | 1 | 1 | 0% | 2,441 | 2,272 | -7% | 0 | 0 | — |
case-19 | fail→pass | 8,295 | 7,254 | -13% | 1 | 1 | 0% | 1,378 | 2,374 | +72% | 0 | 0 | — |
case-20 | pass→pass | 26,731 | 15,130 | -43% | 1 | 1 | 0% | 4,014 | 4,600 | +15% | 0 | 0 | — |
case-21 | pass→pass | 12,222 | 13,679 | +12% | 1 | 1 | 0% | 2,629 | 5,079 | +93% | 0 | 0 | — |
case-22 | pass→pass | 21,929 | 13,825 | -37% | 1 | 1 | 0% | 2,947 | 4,312 | +46% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.