Agent Skills: Ideogram SDK Patterns

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/ideogram-sdk-patterns

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/ideogram-pack/skills/ideogram-sdk-patterns

Skill Files

Browse the full folder contents for ideogram-sdk-patterns.

Download Skill

Loading file tree…

plugins/saas-packs/ideogram-pack/skills/ideogram-sdk-patterns/SKILL.md

Skill Metadata

Name
ideogram-sdk-patterns
Description
|

Ideogram SDK Patterns

Overview

Production-ready patterns for Ideogram's REST API. Since Ideogram has no official SDK, these patterns provide type-safe wrappers, retry logic, response validation, and multi-tenant support for the api.ideogram.ai endpoints.

Prerequisites

  • Completed ideogram-install-auth setup
  • Familiarity with async/await and fetch API
  • Understanding of Ideogram response lifecycle (URLs expire)

Instructions

Step 1: Singleton Client with Auto-Download

// src/ideogram/client.ts
import { writeFileSync, mkdirSync } from "fs";
import { join } from "path";

const API_BASE = "https://api.ideogram.ai";

let instance: IdeogramClient | null = null;

export function getIdeogramClient(): IdeogramClient {
  if (!instance) {
    const key = process.env.IDEOGRAM_API_KEY;
    if (!key) throw new Error("IDEOGRAM_API_KEY not set");
    instance = new IdeogramClient(key);
  }
  return instance;
}

export class IdeogramClient {
  constructor(private apiKey: string) {}

  async generate(prompt: string, options: {
    model?: string;
    style_type?: string;
    aspect_ratio?: string;
    negative_prompt?: string;
    magic_prompt_option?: string;
    num_images?: number;
    seed?: number;
  } = {}) {
    const response = await fetch(`${API_BASE}/generate`, {
      method: "POST",
      headers: { "Api-Key": this.apiKey, "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: options.magic_prompt_option ?? "AUTO",
          negative_prompt: options.negative_prompt,
          num_images: options.num_images ?? 1,
          seed: options.seed,
        },
      }),
    });
    return this.handleResponse(response);
  }

  async describe(imagePath: string) {
    const form = new FormData();
    const file = new Blob([await import("fs").then(fs => fs.readFileSync(imagePath))]);
    form.append("image_file", file, "image.png");
    const response = await fetch(`${API_BASE}/describe`, {
      method: "POST",
      headers: { "Api-Key": this.apiKey },
      body: form,
    });
    return this.handleResponse(response);
  }

  async downloadImage(url: string, outputDir: string, filename?: string): Promise<string> {
    mkdirSync(outputDir, { recursive: true });
    const imgResponse = await fetch(url);
    if (!imgResponse.ok) throw new Error(`Download failed: ${imgResponse.status}`);
    const buffer = Buffer.from(await imgResponse.arrayBuffer());
    const outPath = join(outputDir, filename ?? `ideogram-${Date.now()}.png`);
    writeFileSync(outPath, buffer);
    return outPath;
  }

  private async handleResponse(response: Response) {
    if (!response.ok) {
      const body = await response.text();
      throw new IdeogramApiError(response.status, body);
    }
    return response.json();
  }
}

export class IdeogramApiError extends Error {
  constructor(public status: number, public body: string) {
    super(`Ideogram ${status}: ${body}`);
    this.name = "IdeogramApiError";
  }
  get isRateLimited() { return this.status === 429; }
  get isSafetyRejected() { return this.status === 422; }
  get isAuthError() { return this.status === 401; }
}

Step 2: Retry with Exponential Backoff

export async function withRetry<T>(
  operation: () => Promise<T>,
  config = { maxRetries: 3, baseDelayMs: 1000, maxDelayMs: 30000 }
): Promise<T> {
  for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
    try {
      return await operation();
    } catch (err: any) {
      if (attempt === config.maxRetries) throw err;
      // Only retry on 429 (rate limit) or 5xx (server error)
      const status = err.status ?? err.response?.status;
      if (status && status !== 429 && status < 500) throw err;

      const delay = Math.min(
        config.baseDelayMs * Math.pow(2, attempt) + Math.random() * 500,
        config.maxDelayMs
      );
      console.warn(`Ideogram retry ${attempt + 1}/${config.maxRetries} in ${delay.toFixed(0)}ms`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
  throw new Error("Unreachable");
}

// Usage: await withRetry(() => client.generate("a sunset"));

Step 3: Response Validation with Zod

import { z } from "zod";

const ImageResultSchema = z.object({
  url: z.string().url(),
  prompt: z.string(),
  resolution: z.string(),
  is_image_safe: z.boolean(),
  seed: z.number(),
  style_type: z.string().optional(),
});

const GenerateResponseSchema = z.object({
  created: z.string(),
  data: z.array(ImageResultSchema).min(1),
});

export function validateGenerateResponse(raw: unknown) {
  return GenerateResponseSchema.parse(raw);
}

Step 4: Python Client

# ideogram_client.py
import os, requests, hashlib, time
from pathlib import Path

class IdeogramClient:
    BASE_URL = "https://api.ideogram.ai"

    def __init__(self, api_key: str | None = None):
        self.api_key = api_key or os.environ.get("IDEOGRAM_API_KEY", "")
        if not self.api_key:
            raise ValueError("IDEOGRAM_API_KEY required")

    def generate(self, prompt: str, model="V_2", style_type="AUTO",
                 aspect_ratio="ASPECT_1_1", **kwargs) -> dict:
        resp = requests.post(f"{self.BASE_URL}/generate", headers=self._headers(),
            json={"image_request": {"prompt": prompt, "model": model,
                "style_type": style_type, "aspect_ratio": aspect_ratio,
                "magic_prompt_option": kwargs.get("magic_prompt", "AUTO"),
                **{k: v for k, v in kwargs.items() if k != "magic_prompt"}}})
        resp.raise_for_status()
        return resp.json()

    def download(self, url: str, output_dir: str = "./images") -> str:
        Path(output_dir).mkdir(parents=True, exist_ok=True)
        resp = requests.get(url)
        resp.raise_for_status()
        path = f"{output_dir}/ideogram-{int(time.time())}.png"
        Path(path).write_bytes(resp.content)
        return path

    def _headers(self):
        return {"Api-Key": self.api_key, "Content-Type": "application/json"}

Step 5: Multi-Tenant Factory

const tenantClients = new Map<string, IdeogramClient>();

export function getClientForTenant(tenantId: string): IdeogramClient {
  if (!tenantClients.has(tenantId)) {
    const apiKey = getTenantApiKey(tenantId); // from your secret store
    tenantClients.set(tenantId, new IdeogramClient(apiKey));
  }
  return tenantClients.get(tenantId)!;
}

Error Handling

| Pattern | Use Case | Benefit | |---------|----------|---------| | Singleton | Shared client across modules | Single connection, consistent config | | Retry wrapper | Rate limits and transient errors | Automatic recovery from 429/5xx | | Zod validation | Response validation | Catches API changes at parse time | | Auto-download | Image persistence | Prevents URL expiry data loss | | Multi-tenant | SaaS platforms | Per-customer API key isolation |

Output

  • Type-safe client singleton with generate and describe methods
  • Retry logic with exponential backoff and jitter
  • Runtime validation for API responses
  • Auto-download to prevent URL expiration issues

Resources

Next Steps

Apply patterns in ideogram-core-workflow-a for real-world usage.