Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Implement team-based access control and credit management for Ideogram. Use when managing multiple teams with separate budgets, enforcing content policies, or implementing API key isolation for enterprise Ideogram usage. Trigger with phrases like "ideogram RBAC", "ideogram enterprise", "ideogram teams", "ideogram permissions", "ideogram multi-tenant".
.claude/skills/jeremylongshore-ideogram-enterprise-rbac/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 73% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 27% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 37% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 221% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 30% | 0% |
Implement team-based access control for Ideogram's API. Since Ideogram uses a single API key per account with no built-in roles or scopes, enterprise access control must be implemented at the application layer: separate API keys per team, proxy-based content filtering, per-team budget limits, and usage tracking.
┌──────────────────────────────────────────┐
│ Application Proxy Layer │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Marketing│ │ Product │ │ Social │ │
│ │ API Key │ │ API Key │ │API Key │ │
│ └────┬─────┘ └────┬─────┘ └───┬────┘ │
│ └──────────────┼────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────┐ │
│ │ Content Filter + Budget Enforcer │ │
│ └──────────────────┬─────────────────┘ │
└─────────────────────┼────────────────────┘
▼
Ideogram API (api.ideogram.ai)typescriptinterface TeamConfig { name: string; apiKey: string; // Separate Ideogram API key per team dailyBudgetUSD: number; allowedStyles: string[]; allowedModels: string[]; maxConcurrency: number; contentPolicy: "strict" | "moderate" | "permissive"; } const TEAM_CONFIGS: Record<string, TeamConfig> = { marketing: { name: "Marketing", apiKey: process.env.IDEOGRAM_KEY_MARKETING!, dailyBudgetUSD: 20, allowedStyles: ["DESIGN", "REALISTIC"], allowedModels: ["V_2", "V_2_TURBO"], maxConcurrency: 5, contentPolicy: "strict", }, product: { name: "Product Design", apiKey: process.env.IDEOGRAM_KEY_PRODUCT!, dailyBudgetUSD: 50, allowedStyles: ["DESIGN", "REALISTIC", "RENDER_3D", "GENERAL"], allowedModels: ["V_2", "V_2_TURBO"], maxConcurrency: 8, contentPolicy: "moderate", }, social: { name: "Social Media", apiKey: process.env.IDEOGRAM_KEY_SOCIAL!, dailyBudgetUSD: 10, allowedStyles: ["DESIGN", "ANIME", "GENERAL"], allowedModels: ["V_2_TURBO"], maxConcurrency: 3, contentPolicy: "strict", }, };
typescriptinterface ContentCheck { allowed: boolean; reason?: string; } const BLOCKED_TERMS: Record<string, RegExp[]> = { strict: [ /\b(competitor|trademark|brand)\b/i, /\b(violent|weapon|blood|gore)\b/i, /\b(nsfw|nude|explicit)\b/i, ], moderate: [ /\b(nsfw|nude|explicit)\b/i, ], permissive: [], }; function checkContentPolicy(prompt: string, policy: "strict" | "moderate" | "permissive"): ContentCheck { const patterns = BLOCKED_TERMS[policy] ?? []; for (const pattern of patterns) { if (pattern.test(prompt)) { return { allowed: false, reason: `Blocked by ${policy} policy: ${pattern.source}` }; } } if (prompt.length > 10000) { return { allowed: false, reason: "Prompt exceeds 10,000 character limit" }; } return { allowed: true }; }
typescriptconst dailySpend = new Map<string, number>(); function trackSpend(teamId: string, model: string, numImages: number = 1) { const costPerImage: Record<string, number> = { V_2_TURBO: 0.05, V_2: 0.08, V_2A_TURBO: 0.025, V_2A: 0.04, }; const cost = (costPerImage[model] ?? 0.08) * numImages; const current = dailySpend.get(teamId) ?? 0; dailySpend.set(teamId, current + cost); return current + cost; } function checkBudget(teamId: string): { allowed: boolean; remaining: number } { const config = TEAM_CONFIGS[teamId]; if (!config) return { allowed: false, remaining: 0 }; const spent = dailySpend.get(teamId) ?? 0; const remaining = config.dailyBudgetUSD - spent; return { allowed: remaining > 0, remaining }; } // Reset daily at midnight setInterval(() => { dailySpend.clear(); console.log("Daily budget counters reset"); }, 86400000);
typescriptasync function teamGenerate( teamId: string, prompt: string, options: { style_type?: string; model?: string; aspect_ratio?: string } = {} ) { const config = TEAM_CONFIGS[teamId]; if (!config) throw new Error(`Unknown team: ${teamId}`); // Check content policy const contentCheck = checkContentPolicy(prompt, config.contentPolicy); if (!contentCheck.allowed) { throw new Error(`Content blocked: ${contentCheck.reason}`); } // Check style permission const style = options.style_type ?? "AUTO"; if (style !== "AUTO" && !config.allowedStyles.includes(style)) { throw new Error(`Style ${style} not allowed for team ${config.name}`); } // Check model permission const model = options.model ?? config.allowedModels[0]; if (!config.allowedModels.includes(model)) { throw new Error(`Model ${model} not allowed for team ${config.name}`); } // Check budget const budget = checkBudget(teamId); if (!budget.allowed) { throw new Error(`Daily budget exceeded for team ${config.name}. Remaining: $${budget.remaining.toFixed(2)}`); } // Generate using team's API key const response = await fetch("https://api.ideogram.ai/generate", { method: "POST", headers: { "Api-Key": config.apiKey, "Content-Type": "application/json", }, body: JSON.stringify({ image_request: { prompt, model, style_type: style, aspect_ratio: options.aspect_ratio ?? "ASPECT_1_1", magic_prompt_option: "AUTO", }, }), }); if (!response.ok) throw new Error(`Ideogram API error: ${response.status}`); // Track spending trackSpend(teamId, model); return response.json(); }
typescriptfunction teamUsageReport() { const report = []; for (const [teamId, config] of Object.entries(TEAM_CONFIGS)) { const spent = dailySpend.get(teamId) ?? 0; report.push({ team: config.name, dailyBudget: config.dailyBudgetUSD, spent: spent.toFixed(2), remaining: (config.dailyBudgetUSD - spent).toFixed(2), utilization: `${((spent / config.dailyBudgetUSD) * 100).toFixed(0)}%`, }); } console.table(report); return report; }
Quarterly key rotation process:
1. Create new API key in Ideogram dashboard for each team
2. Update secrets in your secret manager
3. Deploy with new keys to staging, verify
4. Deploy to production
5. Monitor for 48 hours
6. Delete old keys from Ideogram dashboard| Issue | Cause | Solution | |-------|-------|----------| | Budget exceeded | Daily limit hit | Wait for reset or increase limit | | Style not allowed | Team policy restriction | Use an allowed style type | | Content blocked | Prompt failed policy | Rephrase to comply with team policy | | Key not set | Missing env variable | Check team-specific key config |
partnership@ideogram.aiFor migration strategies, see ideogram-migration-deep-dive.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 36,026 | 33,379 | -7% | 1 | 1 | 0% | 5,185 | 8,961 | +73% | 0 | 0 | — |
case-02 | fail→pass | 33,870 | 31,920 | -6% | 1 | 1 | 0% | 6,319 | 8,035 | +27% | 0 | 0 | — |
case-03 | fail→fail | 22,262 | 13,112 | -41% | 1 | 1 | 0% | 3,447 | 4,993 | +45% | 0 | 0 | — |
case-04 | pass→pass | 22,517 | 17,598 | -22% | 1 | 1 | 0% | 2,948 | 5,528 | +88% | 0 | 0 | — |
case-05 | pass→pass | 17,723 | 19,245 | +9% | 1 | 1 | 0% | 3,232 | 5,829 | +80% | 0 | 0 | — |
case-06 | pass→pass | 17,387 | 17,441 | +0% | 1 | 1 | 0% | 2,001 | 4,168 | +108% | 0 | 0 | — |
case-07 | pass→pass | 17,411 | 8,809 | -49% | 1 | 1 | 0% | 1,295 | 2,844 | +120% | 0 | 0 | — |
case-08 | pass→pass | 15,060 | 12,269 | -19% | 1 | 1 | 0% | 2,062 | 3,791 | +84% | 0 | 0 | — |
case-09 | fail→pass | 18,289 | 8,064 | -56% | 1 | 1 | 0% | 2,021 | 2,760 | +37% | 0 | 0 | — |
case-10 | pass→pass | 15,196 | 11,673 | -23% | 1 | 1 | 0% | 1,950 | 3,204 | +64% | 0 | 0 | — |
case-11 | fail→pass | 16,822 | 3,433 | -80% | 1 | 1 | 0% | 847 | 2,717 | +221% | 0 | 0 | — |
case-12 | fail→fail | 31,262 | 35,474 | +13% | 1 | 1 | 0% | 4,731 | 7,816 | +65% | 0 | 0 | — |
case-13 | fail→fail | 23,352 | 36,612 | +57% | 1 | 1 | 0% | 3,462 | 5,941 | +72% | 0 | 0 | — |
case-14 | pass→pass | 24,201 | 13,053 | -46% | 1 | 1 | 0% | 3,153 | 4,459 | +41% | 0 | 0 | — |
case-15 | fail→pass | 13,051 | 5,225 | -60% | 1 | 1 | 0% | 2,315 | 3,017 | +30% | 0 | 0 | — |
case-16 | fail→pass | 18,581 | 6,178 | -67% | 1 | 1 | 0% | 2,346 | 3,329 | +42% | 0 | 0 | — |
case-17 | fail→fail | 22,226 | 17,643 | -21% | 1 | 1 | 0% | 3,078 | 5,594 | +82% | 0 | 0 | — |
case-18 | fail→pass | 22,549 | 18,994 | -16% | 1 | 1 | 0% | 3,034 | 4,634 | +53% | 0 | 0 | — |
case-19 | fail→pass | 16,763 | 13,485 | -20% | 1 | 1 | 0% | 3,160 | 4,991 | +58% | 0 | 0 | — |
case-20 | pass→pass | 11,687 | 4,215 | -64% | 1 | 1 | 0% | 913 | 2,837 | +211% | 0 | 0 | — |
case-21 | pass→pass | 11,010 | 3,820 | -65% | 1 | 1 | 0% | 1,727 | 2,849 | +65% | 0 | 0 | — |
case-22 | pass→pass | 10,189 | 9,388 | -8% | 1 | 1 | 0% | 1,665 | 2,846 | +71% | 0 | 0 | — |
case-23 | fail→pass | 7,207 | 2,186 | -70% | 1 | 1 | 0% | 301 | 2,573 | +755% | 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. 23 cases were attempted. The headline lift of +39 percentage points is the difference between those two pass rates over the 23 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.