Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Manage Ideogram generated image assets, metadata tracking, and lifecycle management. Use when implementing image persistence, tracking generation history, or building asset management for Ideogram outputs. Trigger with phrases like "ideogram data", "ideogram images", "ideogram asset management", "ideogram metadata", "ideogram image storage".
.claude/skills/jeremylongshore-ideogram-data-handling/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 13% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 67% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 69% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 41% | 0% |
Manage generated image assets from Ideogram's API. Critical concern: Ideogram image URLs expire (approximately 1 hour). Every generation must be downloaded and persisted immediately. This skill covers metadata tracking, download pipelines, local and cloud storage, lifecycle management, and generation history for reproducibility.
IDEOGRAM_API_KEY configuredtypescriptinterface GenerationRecord { id: string; // Unique identifier prompt: string; // Original prompt expandedPrompt?: string; // Magic Prompt expansion (from response) negativePrompt?: string; // Negative prompt used model: string; // V_2, V_2_TURBO, etc. styleType: string; // DESIGN, REALISTIC, etc. aspectRatio: string; // ASPECT_16_9, etc. seed: number; // For reproducibility resolution: string; // e.g., "1024x1024" isSafe: boolean; // is_image_safe from response originalUrl: string; // Temporary Ideogram URL storedPath: string; // Local or S3 path createdAt: string; // ISO timestamp sizeBytes?: number; // Downloaded file size tags?: string[]; // User-defined tags }
typescriptimport { writeFileSync, mkdirSync, statSync } from "fs"; import { join } from "path"; import { randomUUID } from "crypto"; const STORAGE_DIR = "./generated-images"; const records: GenerationRecord[] = []; async function generateAndPersist( prompt: string, options: { model?: string; style_type?: string; aspect_ratio?: string; negative_prompt?: string; seed?: number; tags?: string[]; } = {} ): Promise<GenerationRecord> { // Generate const response = await fetch("https://api.ideogram.ai/generate", { method: "POST", headers: { "Api-Key": process.env.IDEOGRAM_API_KEY!, "Content-Type": "application/json", }, body: JSON.stringify({ image_request: { prompt, model: options.model ?? "V_2", style_type: options.style_type ?? "AUTO", aspect_ratio: options.aspect_ratio ?? "ASPECT_1_1", magic_prompt_option: "AUTO", negative_prompt: options.negative_prompt, seed: options.seed, }, }), }); if (!response.ok) throw new Error(`Generation failed: ${response.status}`); const result = await response.json(); const image = result.data[0]; // Download IMMEDIATELY (URLs expire ~1 hour) const imgResp = await fetch(image.url); if (!imgResp.ok) throw new Error(`Download failed: ${imgResp.status}`); const buffer = Buffer.from(await imgResp.arrayBuffer()); mkdirSync(STORAGE_DIR, { recursive: true }); const filename = `${image.seed}-${Date.now()}.png`; const storedPath = join(STORAGE_DIR, filename); writeFileSync(storedPath, buffer); // Track metadata const record: GenerationRecord = { id: randomUUID(), prompt, expandedPrompt: image.prompt !== prompt ? image.prompt : undefined, negativePrompt: options.negative_prompt, model: options.model ?? "V_2", styleType: image.style_type ?? options.style_type ?? "AUTO", aspectRatio: options.aspect_ratio ?? "ASPECT_1_1", seed: image.seed, resolution: image.resolution, isSafe: image.is_image_safe, originalUrl: image.url, storedPath, createdAt: new Date().toISOString(), sizeBytes: buffer.length, tags: options.tags, }; records.push(record); saveRecords(); return record; } function saveRecords() { writeFileSync( join(STORAGE_DIR, "generations.json"), JSON.stringify(records, null, 2) ); }
typescriptimport { S3Client, PutObjectCommand, DeleteObjectCommand } from "@aws-sdk/client-s3"; const s3 = new S3Client({ region: process.env.AWS_REGION }); async function persistToS3(imageUrl: string, seed: number): Promise<string> { const response = await fetch(imageUrl); const buffer = Buffer.from(await response.arrayBuffer()); const key = `ideogram/${seed}-${Date.now()}.png`; await s3.send(new PutObjectCommand({ Bucket: process.env.S3_BUCKET!, Key: key, Body: buffer, ContentType: "image/png", CacheControl: "public, max-age=31536000, immutable", Metadata: { seed: String(seed), source: "ideogram" }, })); return `https://${process.env.CDN_DOMAIN}/${key}`; }
typescript// Reproduce an image using the stored seed and prompt async function reproduceImage(record: GenerationRecord) { return generateAndPersist(record.prompt, { model: record.model, style_type: record.styleType, aspect_ratio: record.aspectRatio, negative_prompt: record.negativePrompt, seed: record.seed, // Same seed = same image tags: [...(record.tags ?? []), "reproduced"], }); }
typescriptimport { unlinkSync, existsSync, readdirSync, statSync } from "fs"; function cleanupOldAssets(retentionDays: number = 30) { const cutoffMs = Date.now() - retentionDays * 86400000; let deleted = 0; let kept = 0; for (const record of records) { const createdMs = new Date(record.createdAt).getTime(); if (createdMs < cutoffMs) { if (existsSync(record.storedPath)) { unlinkSync(record.storedPath); deleted++; } } else { kept++; } } // Remove expired records const activeRecords = records.filter( r => new Date(r.createdAt).getTime() >= cutoffMs ); records.length = 0; records.push(...activeRecords); saveRecords(); console.log(`Cleanup: deleted ${deleted}, kept ${kept}`); } function storageReport() { const totalBytes = records.reduce((sum, r) => sum + (r.sizeBytes ?? 0), 0); const byModel = Object.groupBy(records, r => r.model); console.log("=== Image Storage Report ==="); console.log(`Total images: ${records.length}`); console.log(`Total size: ${(totalBytes / 1024 / 1024).toFixed(1)} MB`); for (const [model, recs] of Object.entries(byModel)) { console.log(` ${model}: ${recs?.length ?? 0} images`); } }
typescriptfunction findByPrompt(searchTerm: string): GenerationRecord[] { return records.filter(r => r.prompt.toLowerCase().includes(searchTerm.toLowerCase()) ); } function findBySeed(seed: number): GenerationRecord | undefined { return records.find(r => r.seed === seed); } function findByTags(tags: string[]): GenerationRecord[] { return records.filter(r => tags.every(t => r.tags?.includes(t)) ); }
| Issue | Cause | Solution | |-------|-------|----------| | Expired URL | Downloaded too late | Always download in same function | | Disk full | Too many stored images | Run cleanupOldAssets() regularly | | Missing metadata | Not tracked at generation | Use generateAndPersist wrapper | | Duplicate prompts | Same prompt run twice | Check by prompt hash before generating | | Lost seed | Not recorded | Always store seed from response |
For access control, see ideogram-enterprise-rbac.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | fail→pass | 12,703 | 10,068 | -21% | 1 | 1 | 0% | 2,724 | 4,247 | +56% | 0 | 0 | — |
case-01 | fail→pass | 25,987 | 18,604 | -28% | 1 | 1 | 0% | 4,596 | 5,187 | +13% | 0 | 0 | — |
case-02 | fail→fail | 31,108 | 16,034 | -48% | 1 | 1 | 0% | 3,108 | 4,449 | +43% | 0 | 0 | — |
case-03 | fail→fail | 22,707 | 23,468 | +3% | 1 | 1 | 0% | 3,841 | 6,334 | +65% | 0 | 0 | — |
case-05 | fail→pass | 8,816 | 8,636 | -2% | 1 | 1 | 0% | 1,703 | 2,851 | +67% | 0 | 0 | — |
case-06 | fail→pass | 15,212 | 10,992 | -28% | 1 | 1 | 0% | 1,971 | 3,327 | +69% | 0 | 0 | — |
case-07 | fail→fail | 15,895 | 12,530 | -21% | 1 | 1 | 0% | 2,755 | 3,547 | +29% | 0 | 0 | — |
case-08 | fail→pass | 11,780 | 5,712 | -52% | 1 | 1 | 0% | 2,131 | 2,999 | +41% | 0 | 0 | — |
case-17 | fail→pass | 17,874 | 2,963 | -83% | 1 | 1 | 0% | 2,762 | 2,713 | -2% | 0 | 0 | — |
case-09 | fail→pass | 17,936 | 14,023 | -22% | 1 | 1 | 0% | 2,700 | 3,805 | +41% | 0 | 0 | — |
case-10 | pass→pass | 16,605 | 8,180 | -51% | 1 | 1 | 0% | 1,547 | 2,623 | +70% | 0 | 0 | — |
case-11 | fail→pass | 7,480 | 9,666 | +29% | 1 | 1 | 0% | 1,547 | 3,124 | +102% | 0 | 0 | — |
case-12 | pass→pass | 17,355 | 8,600 | -50% | 1 | 1 | 0% | 2,308 | 3,763 | +63% | 0 | 0 | — |
case-13 | pass→pass | 13,568 | 10,376 | -24% | 1 | 1 | 0% | 1,943 | 3,640 | +87% | 0 | 0 | — |
case-14 | pass→pass | 16,501 | 14,591 | -12% | 1 | 1 | 0% | 1,880 | 3,874 | +106% | 0 | 0 | — |
case-15 | fail→pass | 17,682 | 13,559 | -23% | 1 | 1 | 0% | 2,331 | 3,613 | +55% | 0 | 0 | — |
case-16 | pass→pass | 19,081 | 12,729 | -33% | 1 | 1 | 0% | 2,010 | 3,736 | +86% | 0 | 0 | — |
case-18 | pass→pass | 12,027 | 8,580 | -29% | 1 | 1 | 0% | 1,181 | 2,754 | +133% | 0 | 0 | — |
case-19 | pass→pass | 22,034 | 3,883 | -82% | 1 | 1 | 0% | 2,493 | 2,819 | +13% | 0 | 0 | — |
case-20 | fail→pass | 17,338 | 10,847 | -37% | 1 | 1 | 0% | 2,303 | 3,426 | +49% | 0 | 0 | — |
case-21 | pass→pass | 15,467 | 17,155 | +11% | 1 | 1 | 0% | 2,958 | 4,868 | +65% | 0 | 0 | — |
case-22 | pass→pass | 15,794 | 9,955 | -37% | 1 | 1 | 0% | 2,094 | 3,112 | +49% | 0 | 0 | — |
case-23 | pass→fail | 42,830 | 25,776 | -40% | 1 | 1 | 0% | 3,573 | 6,425 | +80% | 0 | 0 | — |
case-24 | pass→pass | 20,900 | 26,665 | +28% | 1 | 1 | 0% | 3,098 | 6,538 | +111% | 0 | 0 | — |
case-25 | pass→pass | 16,461 | 20,335 | +24% | 1 | 1 | 0% | 3,260 | 5,234 | +61% | 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. 25 cases were attempted. The headline lift of +36 percentage points is the difference between those two pass rates over the 25 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
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.