mcpField
I
ideogram-image

ideogram-image

github.com/calesthioクリエイティブ・メディア219104 回インストール15 週間前に更新
リポジトリ

ideogram-image について

ideogram-image は、Ideogram の公式 Developer API で画像を生成・編集するためのスキルです。開発者向けに、用途に合うルートやリクエスト形式、変動する API 情報の確認点を整理します。

主な機能

  1. 01用途に応じた現行 API ルートの選択
  2. 02画像の生成・リミックス・編集
  3. 03非同期ジョブの送信と状態確認
  4. 04画像の説明生成と背景削除
  5. 05データセットとカスタムモデルの管理

活用シーン

  1. 01テキストプロンプトからの画像生成
  2. 02参照画像やマスクを使った画像変更
  3. 03画像からの説明プロンプト作成
  4. 04カスタムモデル学習フローの計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/calesthio/generative-media-skills --skill ideogram-image

インストール後、Claude Code で/ideogram-imageを入力して読み込まれたか確認します。

同梱ファイルEVAL.md

詳細

Ideogram 画像

公式 Developer API を直接使用し、以下の確認日を契約の一部として扱ってください。

エビデンスラベルと更新ルール

  • Documented:Ideogram の公式ページまたは公式 OpenAPI ドキュメントに記載されています。
  • Inference:公開されている契約で保証されていない結論です。
  • Production heuristic:運用上のアドバイスであり、Ideogram による保証ではありません。
  • モデルの提供状況、エンドポイントのライフサイクル、料金、制限、ポリシー、一時 URL の有効期間は変動するものとして扱ってください。リリースや見積もりの前に、ドキュメントのインデックス、OpenAPI 3.1 JSON、料金、API 契約、利用ポリシー、プライバシーポリシーを再確認してください。

特に断りがない限り、以下の API に関する事実はDocumented — 2026-07-09 確認済みです。

架空のモデル文字列ではなく、ルートを選択してください

ニーズ正式なルート有効なモデル/ライフサイクル
新しいテキスト/レイアウト生成POST /v1/ideogram-v4/generateIdeogram 4.0;現行の主力 API ルート
非同期の新規生成POST /v1/ideogram-v4/async/generateIdeogram 4.0;Webhook とポーリング
V4 の画像ガイド付き変更POST /v1/ideogram-v4/remixIdeogram 4.0
スタイル/キャラクター参照、マスク編集、リフレーム、背景置換以下の /v1/ideogram-v3/* ルートIdeogram 3.0;現行の特化ルート
プロンプトのみの複数画像編集POST /v1/edit現行の指示による編集ルート;ドキュメントには V3 解像度制御の記載がありますが、リクエストにモデルフィールドはありません
本文でモデルを選択する従来の生成/generate、/edit、/remix、/reframeリファレンスで明示的にレガシーと記載されています。新しい作業では使用しないでください

V4 または V3 のルート固有エンドポイントに model を送信しないでください。モデル識別子はルートセグメントの ideogram-v4 と ideogram-v3 です。レガシーの ModelEnum で指定できるのは V_1、V_1_TURBO、V_2、V_2_TURBO、V_2A、V_2A_TURBO、または AUTO であり、V3/V4 は含まれていません。汎用の Describe ルートでは V_2、V_3、または V_4 を指定できます。V3 カスタムモデルは、model/<model_name>/version/<version_name> 形式の custom_model_uri で指定します。

公式リファレンスには、POST /v1/ideogram-v3/edit はレガシーであり、削除されると明記されています。これを /v1/ideogram-v3/inpaint に移行してください。V3 や残りのレガシールートについて、公式 API の廃止日は発表されていません。アプリのモデル履歴では 0.1 と 0.2 が廃止済みとされていますが、これらは現在の API リクエスト ID ではありません。アプリの履歴から API の廃止スケジュールを推測しないでください。

認証し、通信仕様を確立する

ベース URL https://api.ideogram.ai を使用してください。シークレットは Api-Key: <key> としてのみ送信してください。API の請求/アカウント設定は、Ideogram アプリのサブスクリプションとは別です。キー全体が表示されるのは、作成時の一度だけです。

**Production heuristic:**キーは IDEOGRAM_API_KEY から読み込み、ログではマスキングし、デプロイするシステムではシークレットマネージャーを使用し、接続/読み取りのタイムアウトを設定してください。環境変数名はクライアント側の慣例であり、Ideogram が定義した名前ではありません。

現在の多くの操作では multipart/form-data を使用します。HTTP ライブラリに境界を設定させてください。V4 Magic Prompt は JSON を使用します。生成/編集の成功レスポンスはすべて、埋め込み画像データではなく URL を返します。必要な結果はすべてすぐにダウンロードしてください。公式ドキュメントによると画像リンクには有効期限がありますが、固定の有効期間は公開されていません。出力形式の選択機能は文書化されていないため、URL の拡張子から PNG/JPEG を保証しないでください。ダウンロード後にレスポンスヘッダーとバイト列を確認してください。

文書化された操作のみを使用する

現行の画像およびビジョン機能
メソッドとパス必須リクエスト任意のリクエスト成功時の形式
POST /v1/ideogram-v4/generateform の text_prompt:string または json_prompt:V4JsonPrompt のいずれか一方のみresolution:ResolutionV4、rendering_speed、`enable_copyright_detection:booleannull`
POST /v1/ideogram-v4/async/generate?webhook_url=<https-url>同じ V4 の form。HTTPS の webhook クエリが必須同じ V4 オプション{generation_id:string}
GET /v1/generations/{generation_id}URL セーフな base64 形式の生成 IDなし保留中/失敗のステータス、または完了データ
POST /v1/ideogram-v4/remixform の image:file、text_prompt:stringimage_weight:1..100、V4 の解像度/速度/著作権検出ImageGenerationResponseV4
POST /v1/ideogram-v4/magic-promptJSON の text_prompt:stringaspect_ratio:AspectRatioV4=AUTO{json_prompt:V4JsonPrompt, aspect_ratio}
POST /v1/ideogram-v4/describeform の image_file:file(JPEG/PNG/WebP、最大 10 MB)include_bbox:boolean=true{json_prompt:V4JsonPrompt}
POST /v1/ideogram-v3/generateform の prompt:string以下の V3 生成/スタイル/参照フィールドImageGenerationResponseV3
POST /v1/ideogram-v3/generate-transparentform の prompt:stringseed、`upscale_factor:X1X2
POST /v1/ideogram-v3/inpaintform の image:file、同じサイズの白黒の mask:file、prompt:stringMagic Prompt、生成数、seed、速度、V3 のスタイル/参照フィールドV3 画像レスポンス
POST /v1/ideogram-v3/remixform の image:file、prompt:stringimage_weight=50、seed、サイズ、速度、Magic Prompt、ネガティブプロンプト、生成数、スタイル/参照フィールドV3 画像レスポンス
POST /v1/ideogram-v3/reframeform の image:file、resolution:ResolutionV3生成数、seed、速度、プリセット/パレット/スタイルコード/参照V3 画像レスポンス
POST /v1/ideogram-v3/replace-backgroundform の image:file、prompt:stringMagic Prompt、生成数、seed、速度、プリセット/パレット/スタイルコード/参照V3 画像レスポンス
POST /v1/remove-backgroundform の image:fileなし`{created, data:[{url:string
POST /v1/ideogram-v3/layerize-textform の image:fileprompt:string、seed以下のドキュメントの曖昧さを参照
POST /v1/edit編集対象の入力がある場合、form の prompt:string と images:file[] または image_urls:string[]生成数、seed、Magic Prompt、V3 の解像度またはアスペクト比、transparent_background=false{created,data:V1EditImageObject[]}
POST /upscaleform の image_file:file、image_request JSON オブジェクトオブジェクトのフィールド:prompt、resemblance=50、detail=50、magic_prompt_option、num_images=1、seed従来形式の画像レスポンス
POST /describeform の image_file:file`describe_model_version:V_2V_3

上記のアップロード画像フィールドはすべて JPEG、PNG、WebP を受け付け、/v1/edit を除き最大 10 MB に制限されています。このルートでは、最大 10 MB の画像を最大 10 枚、または Ideogram の画像 URL を最大 10 件受け付けます。V3 のスタイル参照グループとキャラクター参照グループの合計サイズは、それぞれ 10 MB までです。V3 API が現在サポートしているキャラクター参照画像は 1 枚です。対応する任意のグレースケールマスクは、参照画像の数と寸法に一致している必要があります。

OpenAPI では、カスタムモデルの管理用に GET/POST /datasets、GET /datasets/{dataset_id}、POST /datasets/{dataset_id}/upload_assets、POST /v1/ideogram-v3/train-model、GET /models、GET /models/{model_id} も記載されています。現在の学習ルート/OpenAPI の契約では、データセット画像が 15~100 枚必要ですが、公式チュートリアルでは 10 枚から学習を開始できるとされています。Ideogram がこの不一致を解消するまでは少なくとも 15 枚を使用し、40 ドルの実行前に検証して、使用した最新のスキーマを記録してください。model_name は 5~30 文字の英数字/スペース/ハイフンで指定します。これらの操作は、カスタムモデルに関する明示的なタスクでのみ使用してください。 従来版でのみドキュメント化されている操作は、POST /generate、/v1/ideogram-v3/edit、/edit、/remix、/reframe です。移行中に限りこれらを保持し、ドキュメント化されていない v4/edit、v4/inpaint、v4/reframe、v4/replace-background のルートを作成しないでください。

正確な共通スキーマ

V4JsonPrompt:

{
  "high_level_description": "required string",
  "style_description": {
    "aesthetics": "optional string",
    "art_style": "optional string",
    "lighting": "optional string",
    "medium": "optional string",
    "photo": "optional string"
  },
  "compositional_deconstruction": {
    "background": "required string",
    "elements": [
      {"type": "obj", "desc": "required string", "bbox": [0, 0, 1000, 1000]},
      {"type": "text", "text": "required literal string", "desc": "required string", "bbox": [0, 0, 1000, 1000]}
    ]
  }
}

high_level_description、compositional_deconstruction.background、elements は必須です。オブジェクトには type と desc が必要です。テキストには type、リテラルの text、desc が必要です。style_description と各 bbox は任意です。bbox は、1000×1000 の座標系における、正規化された4つの整数 [y_min,x_min,y_max,x_max] として解釈してください。説明文では [0,1000] の4つの値と定義されていますが、公開されている JSON Schema には minItems、maxItems、数値の境界が記述されていないため、クライアント側で検証してください。

V4 の解像度は、正確には次のとおりです。

2048x2048, 1440x2880, 2880x1440, 1664x2496, 2496x1664, 1792x2240,
2240x1792, 1440x2560, 2560x1440, 1600x2560, 2560x1600, 1728x2304,
2304x1728, 1296x3168, 3168x1296, 1152x2944, 2944x1152, 1248x3328,
3328x1248, 1280x3072, 3072x1280, 1024x3072, 3072x1024

V4 Magic Prompt のアスペクト比は AUTO、1x4、1x3、1x2、9x16、10x16、2x3、3x4、4x5、1x1、5x4、4x3、3x2、16x10、16x9、2x1、3x1、4x1 です。

V3 のアスペクト比は 1x3、3x1、1x2、2x1、9x16、16x9、10x16、16x10、2x3、3x2、3x4、4x3、4x5、5x4、1x1 です。V3 では aspect_ratio と resolution を同時に送信しないでください。デフォルトの比率は 1x1 です。ResolutionV3 はOpenAPI スキーマに記載された正確な69個の値からなる enum です。任意の寸法を概算するのではなく、現在の enum に照らして検証してください。

V3 の num_images は1~8で、デフォルトは1です。V3 の seed は0~2147483647です。V4 の generate/remix では、枚数もリクエストのシード値も公開されていません。レスポンスにはシード値が返されます。レスポンスの配列の例から、制御可能な枚数を推測しないでください。

rendering_speed は FLASH|TURBO|DEFAULT|QUALITY で、デフォルトは DEFAULT です。**変動する、ドキュメント化された例外:**現在、V4 は FLASH に対して400を返します。透明背景の生成でも FLASH は拒否されます。V3 のスタイルタイプは AUTO|GENERAL|REALISTIC|DESIGN|FICTION で、デフォルトは GENERAL です。V3 の Magic Prompt は AUTO|ON|OFF です。

V3 のスタイルプリセットは、正確には次のとおりです。

80S_ILLUSTRATION, 90S_NOSTALGIA, ABSTRACT_ORGANIC, ANALOG_NOSTALGIA, ART_BRUT,
ART_DECO, ART_POSTER, AURA, AVANT_GARDE, BAUHAUS, BLUEPRINT, BLURRY_MOTION,
BRIGHT_ART, C4D_CARTOON, CHILDRENS_BOOK, COLLAGE, COLORING_BOOK_I, COLORING_BOOK_II,
CUBISM, DARK_AURA, DOODLE, DOUBLE_EXPOSURE, DRAMATIC_CINEMA, EDITORIAL,
EMOTIONAL_MINIMAL, ETHEREAL_PARTY, EXPIRED_FILM, FLAT_ART, FLAT_VECTOR,
FOREST_REVERIE, GEO_MINIMALIST, GLASS_PRISM, GOLDEN_HOUR, GRAFFITI_I, GRAFFITI_II,
HALFTONE_PRINT, HIGH_CONTRAST, HIPPIE_ERA, ICONIC, JAPANDI_FUSION, JAZZY,
LONG_EXPOSURE, MAGAZINE_EDITORIAL, MINIMAL_ILLUSTRATION, MIXED_MEDIA, MONOCHROME,
NIGHTLIFE, OIL_PAINTING, OLD_CARTOONS, PAINT_GESTURE, POP_ART, RETRO_ETCHING,
RIVIERA_POP, SPOTLIGHT_80S, STYLIZED_RED, SURREAL_COLLAGE, TRAVEL_POSTER,
VINTAGE_GEO, VINTAGE_POSTER, WATERCOLOR, WEIRD, WOODBLOCK_PRINT

カラーパレットのバリエーションは、{"name":"EMBER|FRESH|JUNGLE|MAGIC|MELON|MOSAIC|PASTEL|ULTRAMARINE"} または {"members":[{"color_hex":"#RRGGBB","color_weight":0.05..1.0}]} のいずれか1つを使用してください。8文字の16進数の style_codes を使用してください。style_codes を style_reference_images または style_type と組み合わせないでください。

共通のV4成功レスポンス:

{"created":"date-time","data":[{"url":"uri or null","prompt":"string","resolution":"ResolutionV4","is_image_safe":true,"seed":12345}],"response_type":"url"}

共通のV3成功レスポンスには、任意の upscaled_resolution と style_type が追加されます。created、data、および各アイテムの prompt、resolution、is_image_safe、seed は必須です。is_image_safe が false の場合、url は空または null です。ポーリングには、generation_id、status:pending|completed|failed、created が必要です。response_type:"url" と data を含むのは、完了したレスポンスだけです。

補助的な正確なレスポンス:/upscale は、従来版の ImageObject エントリーを使う {created, request_id?, data?} を返します。/describe は {descriptions:[{text?:string}]} を返します。/v1/ideogram-v4/describe は {json_prompt:V4JsonPrompt} を返します。/v1/remove-background は、アイテムが正確に1つの {created,data:[{url?:string|null,is_image_safe:boolean}]} を返します。OpenAPI では、通常の例に含まれているにもかかわらず、descriptions と各 text は形式上は任意とされています。使用する前に、これらが存在することを検証してください。

テキスト、レイアウト、スタイル、参照を制御する

V4 のタイポグラフィでは、json_prompt を使用し、正確なコピーを type:"text" 要素の text に配置してください。フォントの種類、ウェイト、大文字・小文字、色、配置、役割は desc で説明し、レイアウトには bbox を使用します。text_prompt は Magic Prompt を自動的に有効にしますが、json_prompt はこれをバイパスし、直接処理されます。まず /v1/ideogram-v4/magic-prompt で構造化 JSON を生成するか、画像から /v1/ideogram-v4/describe で復元してください。

ファーストパーティの API スキーマには、プロンプトの最大長、リテラルテキストの最大長、保証されるフォントカタログ、正確な表記の SLA は公開されていません。値を捏造しないでください。本番環境でのヒューリスティック: 重要なコピーを短く保ち、コピー領域をそれぞれ独立させ、ほかの場所に矛盾する視覚的テキストを避け、複数のバリエーションを生成し、OCR と人による確認で正確な表記を検証してください。

V3 では、表示するコピーをプロンプト内にはっきり記述し、適切な場合は DESIGN を選択してください。既知の美的スタイルには style_preset を使用し、ビジュアルの転写にはスタイル参照を、アイデンティティの一貫性にはキャラクター参照を、ソースの保持には Remix の image_weight を使用します。V4 Generate ルートが V3 のスタイル/キャラクター参照フィールドを受け付けると主張しないでください。現行の OpenAPI では受け付けません。

インペインティングでは、マスクを入力と同じ寸法にし、編集可能なピクセルを黒で指定してください。リフレームでは、アスペクト比フィールドではなく V3 の解像度を指定してください。背景の置き換えでは、前景の被写体を保ちながら、希望する新しい背景だけを説明してください。

完全な例を実行する

以下は例であり、普遍的なクライアントアーキテクチャではありません。資金がチャージされたキーを使って意図的に実行した場合にのみ、有料の API 呼び出しを行います。

例:同期 V4 構造化タイポグラフィと永続的なダウンロード

本番環境での目的: スタイルのランダム性よりも見出しが重要な、1:1 のキャンペーンポスターを作成します。この構造を使う理由: json_prompt で自動的な Magic Prompt の書き換えをバイパスし、リテラルコピーを分離して明示的に配置します。想定される結果: 正確な見出し候補と 2048×2048 の結果を含む、安全なレスポンス項目が 1 つ以上返されます。起こりやすい失敗: コピーの誤字、bbox の詰め込み、安全性チェック後の URL が null になること、またはダウンロードの有効期限切れです。有意義なバリエーション: text は固定したまま desc だけを変更してタイポグラフィをテストするか、構図を試行錯誤する場合は先に /magic-prompt を使用します。

import hashlib
import json
import os
from io import BytesIO
from pathlib import Path

import requests
from PIL import Image

API = "https://api.ideogram.ai"
KEY = os.environ["IDEOGRAM_API_KEY"]
prompt = {
    "high_level_description": "A premium square coffee poster with one centered headline.",
    "style_description": {"medium": "screen-printed poster", "aesthetics": "minimal, warm"},
    "compositional_deconstruction": {
        "background": "Flat cream paper with subtle grain.",
        "elements": [{
            "type": "text",
            "text": "MORNING, MADE BOLD",
            "desc": "Large centered uppercase condensed black headline",
            "bbox": [360, 100, 600, 900],
        }],
    },
}
with requests.Session() as session:
    response = session.post(
        f"{API}/v1/ideogram-v4/generate",
        headers={"Api-Key": KEY},
        files={
            "json_prompt": (None, json.dumps(prompt)),
            "resolution": (None, "2048x2048"),
            "rendering_speed": (None, "TURBO"),
        },
        timeout=(10, 180),
    )
    response.raise_for_status()
    manifest = {"request": {"json_prompt": prompt, "resolution": "2048x2048",
                            "rendering_speed": "TURBO"}, "results": []}
    signatures = {
        "image/png": (b"\x89PNG\r\n\x1a\n", ".png"),
        "image/jpeg": (b"\xff\xd8\xff", ".jpg"),
        "image/webp": (b"RIFF", ".webp"),
    }
    for index, item in enumerate(response.json()["data"], start=1):
        if not item["is_image_safe"] or not item.get("url"):
            raise RuntimeError(f"Ideogram candidate {index} is unusable")
        with session.get(item["url"], timeout=(10, 60), stream=True) as download:
            download.raise_for_status()
            limit = 50 * 1024 * 1024
            declared = int(download.headers.get("content-length", "0") or 0)
            if declared > limit:
                raise RuntimeError("Declared image exceeds local 50 MiB safety limit")
            body = bytearray()
            for chunk in download.iter_content(1024 * 1024):
                body.extend(chunk)
                if len(body) > limit:
                    raise RuntimeError("Streaming image exceeded local 50 MiB safety limit")
            raw = bytes(body)
            media_type = download.headers.get("content-type", "").split(";", 1)[0].lower()
        if media_type not in signatures or not raw.startswith(signatures[media_type][0]):
            raise RuntimeError(f"Unexpected image payload: {media_type}")
        if media_type == "image/webp" and raw[8:12] != b"WEBP":
            raise RuntimeError("RIFF payload is not WebP")
        with Image.open(BytesIO(raw)) as decoded:
            decoded.verify()
        with Image.open(BytesIO(raw)) as decoded:
            actual_size = decoded.size
        expected_size = tuple(map(int, item["resolution"].lower().split("x")))
        if actual_size != expected_size:
            raise RuntimeError(f"Expected {expected_size}, decoded {actual_size}")
        output = Path(f"poster-{index}{signatures[media_type][1]}")
        temporary = output.with_suffix(output.suffix + ".tmp")
        temporary.write_bytes(raw)
        temporary.replace(output)
        manifest["results"].append({**item, "path": str(output),
                                    "download_content_type": media_type,
                                    "sha256": hashlib.sha256(raw).hexdigest()})
    Path("poster.metadata.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
例:スタイル参照を使った V3 インペイント

本番環境での目的: 承認済みのビジュアルスタイルを取り入れながら、選択したオブジェクトだけを置き換えます。この構造を使う理由: V3 Inpaint は文書化されたマスク用ルートであり、スタイル参照パーツを繰り返し指定できます。想定される結果: 黒いマスクの外側のピクセルを保持する、2 つの候補が生成されます。起こりやすい失敗: マスクの意味を逆に解釈すること、寸法の不一致、目に見える継ぎ目、またはスタイルのずれです。有意義なバリエーション: ニュートラルな編集ではスタイル参照を省略します。アイデンティティの維持が必要な場合は、サポートされている単一のキャラクター参照を、対応するグレースケールの参照マスクとともに追加します。

import hashlib
import json
import os
from io import BytesIO
from pathlib import Path

import requests
from PIL import Image

API = "https://api.ideogram.ai"
image_path, mask_path, style_path = map(Path, ("input.png", "mask.png", "style.png"))
mime_by_format = {"PNG": "image/png", "JPEG": "image/jpeg", "WEBP": "image/webp"}
formats = {}
for path in (image_path, mask_path, style_path):
    if path.stat().st_size > 10 * 1024 * 1024:
        raise ValueError(f"{path} exceeds 10 MB")
    with Image.open(path) as check:
        check.verify()
        if check.format not in mime_by_format:
            raise ValueError(f"Unsupported image format for {path}: {check.format}")
        formats[path] = check.format
with Image.open(image_path) as source_check, Image.open(mask_path) as mask_check:
    if source_check.size != mask_check.size:
        raise ValueError("mask must match input dimensions")
    if mask_check.mode not in {"1", "L"}:
        raise ValueError("mask must be binary/grayscale")
    if set(mask_check.convert("L").getdata()) - {0, 255}:
        raise ValueError("mask must contain only black and white pixels")
if style_path.stat().st_size > 10 * 1024 * 1024:
    raise ValueError("style-reference group exceeds 10 MB")
with image_path.open("rb") as image, mask_path.open("rb") as mask, style_path.open("rb") as style:
    response = requests.post(
        f"{API}/v1/ideogram-v3/inpaint",
        headers={"Api-Key": os.environ["IDEOGRAM_API_KEY"]},
        data={"prompt": "Replace only the black mask region with a red enamel teapot", "num_images": "2", "seed": "42"},
        files=[("image", (image_path.name, image, mime_by_format[formats[image_path]])),
               ("mask", (mask_path.name, mask, mime_by_format[formats[mask_path]])),
               ("style_reference_images", (style_path.name, style, mime_by_format[formats[style_path]]))],
        timeout=(10, 180),
    )
response.raise_for_status()
payload = response.json()
manifest = {
    "endpoint": "/v1/ideogram-v3/inpaint",
    "request": {"prompt": "Replace only the black mask region with a red enamel teapot", "num_images": 2, "seed": 42},
    "input_sha256": {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in (image_path, mask_path, style_path)},
    "results": [],
}
for index, item in enumerate(payload["data"], start=1):
    if not item["is_image_safe"] or not item.get("url"):
        raise RuntimeError(f"Candidate {index} is not usable")
    with requests.get(item["url"], timeout=(10, 60), stream=True) as download:
        download.raise_for_status()
        limit = 50 * 1024 * 1024
        declared = int(download.headers.get("content-length", "0") or 0)
        if declared > limit:
            raise RuntimeError("Declared image exceeds local 50 MiB safety limit")
        body = bytearray()
        for chunk in download.iter_content(1024 * 1024):
            body.extend(chunk)
            if len(body) > limit:
                raise RuntimeError("Streaming image exceeded local 50 MiB safety limit")
        raw = bytes(body)
        media_type = download.headers.get("content-type", "").split(";", 1)[0].lower()
    signatures = {"image/png": (b"\x89PNG\r\n\x1a\n", ".png"),
                  "image/jpeg": (b"\xff\xd8\xff", ".jpg"),
                  "image/webp": (b"RIFF", ".webp")}
    if media_type not in signatures or not raw.startswith(signatures[media_type][0]):
        raise RuntimeError(f"Unexpected output type: {media_type}")
    if media_type == "image/webp" and raw[8:12] != b"WEBP":
        raise RuntimeError("RIFF payload is not WebP")
    with Image.open(BytesIO(raw)) as decoded:
        decoded.verify()
    with Image.open(BytesIO(raw)) as decoded:
        actual_size = decoded.size
    expected_size = tuple(map(int, item["resolution"].lower().split("x")))
    if actual_size != expected_size:
        raise RuntimeError(f"Expected {expected_size}, decoded {actual_size}")
    output = Path(f"inpaint-{index}{signatures[media_type][1]}")
    temporary = output.with_suffix(output.suffix + ".tmp")
    temporary.write_bytes(raw)
    temporary.replace(output)
    manifest["results"].append({**item, "path": str(output), "content_type": media_type,
                                "sha256": hashlib.sha256(raw).hexdigest()})
Path("inpaint.metadata.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
例:V4 を非同期で送信し、安全にポーリングする

本番環境での目的: HTTP 接続を開いたままにせずに処理を送信し、Webhook の配信に失敗した場合にも復旧できるようにします。この構造を使う理由: サーバーから発行された ID を永続化し、ポーリングを上限付きのフォールバックにします。想定される結果: pending から completed に移行してダウンロード可能なデータが返されるか、終端状態の failed に移行します。起こりやすい失敗: 公開されていない Webhook URL、スロットリング、配信の欠落、またはジョブがいつまでも保留状態になることです。有意義なバリエーション: 検証済みの Webhook が先にレコードを完了させ、ポーリングは主要な完了経路ではなく、照合作業として機能するようにします。

import hashlib
import json
import os
import random
import time
from io import BytesIO
from pathlib import Path

import requests
from PIL import Image

API = "https://api.ideogram.ai"
headers = {"Api-Key": os.environ["IDEOGRAM_API_KEY"]}
webhook_url = os.environ["IDEOGRAM_WEBHOOK_URL"]
if not webhook_url.startswith("https://"):
    raise ValueError("IDEOGRAM_WEBHOOK_URL must be public HTTPS")
accepted = requests.post(
    f"{API}/v1/ideogram-v4/async/generate",
    params={"webhook_url": webhook_url},
    headers=headers,
    files={"text_prompt": (None, "A crisp editorial photograph of a red bicycle")},
    timeout=(10, 30),
)
accepted.raise_for_status()
generation_id = accepted.json()["generation_id"]
Path("async.accepted.json").write_text(json.dumps({
    "generation_id": generation_id,
    "submitted_unix": int(time.time()),
    "webhook_url_sha256": hashlib.sha256(webhook_url.encode("utf-8")).hexdigest(),
}, indent=2), encoding="utf-8")
deadline = time.monotonic() + 10 * 60
completed = False
for attempt in range(40):
    if time.monotonic() >= deadline:
        break
    try:
        result = requests.get(
            f"{API}/v1/generations/{generation_id}", headers=headers, timeout=(10, 30)
        )
    except (requests.Timeout, requests.ConnectionError):
        time.sleep(min(30, 2 ** min(attempt, 5)) + random.random())
        continue
    if result.status_code == 429:
        time.sleep(min(30, 2 ** min(attempt, 5)) + random.random())
        continue
    if result.status_code in {500, 502, 503, 504}:
        time.sleep(min(30, 2 ** min(attempt, 5)) + random.random())
        continue
    result.raise_for_status()
    payload = result.json()
    if payload["status"] == "completed":
        manifest = {"generation_id": generation_id, "results": []}
        for index, item in enumerate(payload["data"], start=1):
            if not item["is_image_safe"] or not item.get("url"):
                raise RuntimeError(f"Completed candidate {index} is unusable")
            with requests.get(item["url"], timeout=(10, 60), stream=True) as download:
                download.raise_for_status()
                limit = 50 * 1024 * 1024
                declared = int(download.headers.get("content-length", "0") or 0)
                if declared > limit:
                    raise RuntimeError("Declared image exceeds local 50 MiB safety limit")
                body = bytearray()
                for chunk in download.iter_content(1024 * 1024):
                    body.extend(chunk)
                    if len(body) > limit:
                        raise RuntimeError("Streaming image exceeded local 50 MiB safety limit")
                raw = bytes(body)
                media_type = download.headers.get("content-type", "").split(";", 1)[0].lower()
            signatures = {"image/png": (b"\x89PNG\r\n\x1a\n", ".png"),
                          "image/jpeg": (b"\xff\xd8\xff", ".jpg"),
                          "image/webp": (b"RIFF", ".webp")}
            if media_type not in signatures or not raw.startswith(signatures[media_type][0]):
                raise RuntimeError(f"Unexpected output type: {media_type}")
            if media_type == "image/webp" and raw[8:12] != b"WEBP":
                raise RuntimeError("RIFF payload is not WebP")
            with Image.open(BytesIO(raw)) as decoded:
                decoded.verify()
            with Image.open(BytesIO(raw)) as decoded:
                actual_size = decoded.size
            expected_size = tuple(map(int, item["resolution"].lower().split("x")))
            if actual_size != expected_size:
                raise RuntimeError(f"Expected {expected_size}, decoded {actual_size}")
            output = Path(f"async-{index}{signatures[media_type][1]}")
            temporary = output.with_suffix(output.suffix + ".tmp")
            temporary.write_bytes(raw)
            temporary.replace(output)
            manifest["results"].append({**item, "path": str(output), "content_type": media_type,
                                        "sha256": hashlib.sha256(raw).hexdigest()})
        Path("async.metadata.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
        completed = True
        break
    if payload["status"] == "failed":
        raise RuntimeError(f"generation failed: {generation_id}")
    time.sleep(min(30, 2 ** min(attempt, 5)) + random.random())
if not completed:
    raise TimeoutError(f"generation still pending at deadline: {generation_id}")
例:Ideogram の Webhook を検証し、永続的に受け付ける

本番環境での意図: 保存する前に未加工の配信データを認証し、古いリクエストや不一致のあるリクエストを拒否し、重複は冪等に確認応答します。この構成にする理由: Ideogram は未加工のバイト列に署名し、再配信する場合があります。SQLite は本番環境のトランザクション対応データストアの代わりとして使用します。期待される結果: 有効な初回配信は一度だけ保存され、HTTP 204 を返します。有効な重複配信も、重複処理を行わずに 204 を返します。起こりやすい失敗: タイムスタンプが古い、16 進数形式が不正、キーが不明またはローテーション済み、本文の再シリアライズ、生成 ID の不一致、JSON が無効。有意義な変更点: SQLite を、アプリケーションの永続データベースにおけるトランザクション対応の一意制約に置き換え、コミット後にワーカーをキューに登録します。

import base64
import hashlib
import json
import sqlite3
import time

import requests
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

JWKS_URL = "https://api.ideogram.ai/v1/.well-known/jwks.json"
H_GENERATION = "X-Ideogram-Webhook-Generation-Id"
H_USER = "X-Ideogram-Webhook-User-Id"
H_TIMESTAMP = "X-Ideogram-Webhook-Timestamp"
H_KEY = "X-Ideogram-Webhook-Key-Id"
H_SIGNATURE = "X-Ideogram-Webhook-Signature"
_JWKS_CACHE = {"keys": None, "expires": 0.0}


def b64url_decode(value: str) -> bytes:
    return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))


def fetch_jwks(*, force_refresh: bool = False, ttl_seconds: int = 300) -> list[dict]:
    now = time.monotonic()
    if not force_refresh and _JWKS_CACHE["keys"] is not None and now < _JWKS_CACHE["expires"]:
        return _JWKS_CACHE["keys"]
    response = requests.get(JWKS_URL, timeout=(5, 15))
    response.raise_for_status()
    keys = response.json()["keys"]
    _JWKS_CACHE.update({"keys": keys, "expires": now + ttl_seconds})
    return keys


def verify_with_keys(keys: list[dict], key_hint: str, signature: bytes, message: bytes) -> bool:
    ordered = sorted(keys, key=lambda key: 0 if key.get("kid") == key_hint else 1)
    for key in ordered:
        if key.get("kty") != "OKP" or key.get("crv") != "Ed25519" or not key.get("x"):
            continue
        try:
            Ed25519PublicKey.from_public_bytes(b64url_decode(key["x"])).verify(signature, message)
            return True
        except (InvalidSignature, ValueError):
            continue
    return False


def verify_delivery(headers, raw_body: bytes, *, max_age_seconds: int = 300) -> dict:
    required = [H_GENERATION, H_USER, H_TIMESTAMP, H_KEY, H_SIGNATURE]
    missing = [name for name in required if not headers.get(name)]
    if missing:
        raise ValueError(f"Missing webhook headers: {missing}")
    timestamp = int(headers[H_TIMESTAMP])
    if abs(int(time.time()) - timestamp) > max_age_seconds:
        raise ValueError("Stale webhook timestamp")
    signature_hex = headers[H_SIGNATURE]
    if signature_hex != signature_hex.lower():
        raise ValueError("Signature must be lowercase hexadecimal")
    try:
        signature = bytes.fromhex(signature_hex)
    except ValueError as exc:
        raise ValueError("Invalid signature hexadecimal") from exc
    body_hash = hashlib.sha256(raw_body).hexdigest()
    message = "\n".join([
        headers[H_GENERATION], headers[H_USER], headers[H_TIMESTAMP], body_hash
    ]).encode("utf-8")
    keys = fetch_jwks()
    if not verify_with_keys(keys, headers[H_KEY], signature, message):
        # Refresh once so key rotation does not require a process restart.
        if not verify_with_keys(fetch_jwks(force_refresh=True), headers[H_KEY], signature, message):
            raise ValueError("Webhook signature verification failed")
    payload = json.loads(raw_body)
    if payload.get("generation_id") != headers[H_GENERATION]:
        raise ValueError("Header/body generation_id mismatch")
    return payload


def handle_webhook(headers, raw_body: bytes, database_path="ideogram-webhooks.sqlite") -> int:
    payload = verify_delivery(headers, raw_body)
    generation_id = payload["generation_id"]
    with sqlite3.connect(database_path) as db:
        db.execute("""CREATE TABLE IF NOT EXISTS deliveries (
            generation_id TEXT PRIMARY KEY, payload_json TEXT NOT NULL,
            received_unix INTEGER NOT NULL, status TEXT NOT NULL
        )""")
        db.execute(
            "INSERT OR IGNORE INTO deliveries VALUES (?, ?, ?, 'received')",
            (generation_id, json.dumps(payload, separators=(",", ":")), int(time.time())),
        )
    # Framework adapter should return this after commit; a worker processes 'received' rows.
    return 204

4 つの Python の例で使用する requests、Pillow、cryptography をインストールします。Web フレームワークは、変更していないリクエスト本文のバイト列と元のヘッダーを handle_webhook に渡す必要があります。先に解析して再シリアライズしてはいけません。

Webhook 署名は、公式 Webhook ガイドの仕様どおりに検証します。未加工の本文バイト列を SHA-256 でハッシュ化し、生成 ID、ユーザー ID、Unix タイムスタンプ、本文ハッシュを改行で連結して、GET /v1/.well-known/jwks.json から取得したキーを使って小文字の 16 進数形式の Ed25519 署名を検証します。ハッシュ化する前に JSON を再シリアライズしてはいけません。本番環境向けの指針: キーをキャッシュし、検証失敗時やキーのローテーション時に更新します。ローカルで設定した許容時間を使って古いタイムスタンプを拒否し、ヘッダーと本文の ID が一致することを必須とし、generation_id をキーとしてトランザクション内で配信の重複を排除します。

ライフサイクル、エラー、再試行、並行処理の管理

デフォルトの上限は同時進行中のリクエスト 10 件です。より多くの量を処理する場合は Ideogram に問い合わせてください。この上限が示すのは同時実行数であり、明記された 1 分あたりのリクエスト数の上限ではありません。

ステータス明記されている意味対応
400入力が無効。現在サポートされていない速度の組み合わせも含みますリクエストを修正します。同じ内容のまま再試行しないでください
401認証情報がない、または無効です停止し、シークレットまたはアカウントを修正します
402/v1/edit でクレジットまたはクォータが不足していますクレジットを補充するか、承認済みプランに変更します。繰り返し再試行しないでください
403V3 Remix/Upscale など、明記されているルートでアクセスが禁止されているか、認可されていません停止し、アカウントの利用資格とルートへのアクセス権を確認します。同じ内容のまま再試行しないでください
404非同期の生成 ID が不明です保存済みの ID と環境を確認します
422プロンプトまたは画像が安全性チェックに失敗しました。明記されている本文は { "error": "string" } です回避しようとしないでください。ポリシーに従って修正するか、拒否します
429リクエストが多すぎます同時進行中の処理を減らし、上限付きの指数バックオフとジッターで再試行します
503/v1/edit など、一部のルートでサービスが一時的に利用できませんバックオフ後、上限付きで再試行します

OpenAPI では、ほとんどの 400/401/429/503 の本文の型は定義されていません。防御的に解析し、ステータス、リクエストのメタデータ、秘匿化または切り詰めを行った本文をログに記録してください。Retry-After が必ず提供されるとは記載されていません。実際に存在する場合はそれに従い、存在しない場合はフルジッターを使用してください。

リクエストの冪等性キーを示すヘッダーは明記されていません。本番環境向けの指針: タイムアウトした同期 POST を、完了して課金されている可能性を考慮せず、決してそのまま再試行してはいけません。V4 の非同期処理を優先し、他の作業に進む前に generation_id を永続化して、ポーリング用の GET のみを再試行します。Ideogram は Webhook を限られた回数だけ再試行する場合がありますが、配信は保証されません。重複配信が成功した場合も 2xx で確認応答し、ポーリングに切り替えてください。

価格とゲート支出

変動する公表価格 — ページには最終更新日が2025-08-06と記載されています。確認日は2026-07-09です: V4 Turbo/Default/Qualityの料金は、出力1件あたり$0.03/$0.06/$0.10です。V3 Flash/Turbo/Default/Qualityは$0.03/$0.03/$0.06/$0.09です。キャラクター参照を使うV3 Turbo/Default/Qualityは$0.10/$0.15/$0.20です。透過対応のV3 X1はTurbo/Default/Qualityで$0.04/$0.07/$0.10、2Xは$0.16/$0.19/$0.22、4Xは$0.20/$0.23/$0.26です。指示による編集は出力1件あたり$0.20、アップスケールは入力1件あたり$0.06、Describeは入力1件あたり$0.01、Layerizeは入力1件あたり$0.09、セルフサービスのカスタムモデルのトレーニングは1回あたり$40です。見積もりを出す前に、最新のページを確認してください。

価格ページには、価格のみが記載されている機能や、ルートが完全には公開されていない機能も記載されています。Geminiによる生成は1K/2Kで$0.20、4Kで$0.36です。Topazによるアップスケールは2K/4K/8Kで$0.12/$0.24/$0.48です。Generate + Layerize V3はTurbo/Default/Qualityで$0.12/$0.15/$0.18です。カスタム推論はV4が$0.06/$0.12/$0.20、V3が$0.06/$0.12/$0.18です。これらの項目について、存在しないエンドポイントを作り出してはいけません。現在公開されているOpenAPIでは、価格表にあるすべての機能のルートが公開されているわけではなく、V3のcustom_model_uriのみが記載されています。対象アカウントで利用できるファーストパーティのリクエストスキーマが提供されるまでは、未解決として扱ってください。

送信前に、上限額を計算してください。要求する出力数×出力1件あたりの料金に、参照画像/アップスケール/編集の追加料金を加えます。APIの自動チャージの動作は、オペレーターに明示してください。セットアップのドキュメントには、設定可能な残高しきい値/チャージ額と、別枠のAPIクレジットについて記載されています。

安全性、プライバシー、来歴、権利を徹底する

is_image_safeだけで判断せず、API契約および利用ポリシーに従ってください。違法なコンテンツ、権利侵害コンテンツ、同意のないコンテンツ、プライバシーを侵害するコンテンツ、ポルノ、過度に暴力的なコンテンツ、憎悪的なコンテンツ、欺瞞的なコンテンツ、いじめ、政治キャンペーンやロビー活動に関するコンテンツ、または有害・犯罪的なコンテンツは送信しないでください。フィルターやレート制限を回避してはいけません。

アップロードする画像、キャラクター参照、肖像、商標、フォント/ブランド素材のそれぞれについて、権利と同意を取得してください。Ideogramの一般利用規約では、ユーザー入力や出力の所有権を主張せず、商用利用を制限しないとされていますが、法令および第三者の権利に対する責任はユーザーにあります。また、出力は一意とは限らず、権利を侵害しないことも保証されていません。これは運用上の指針であり、法的助言ではありません。

API契約では、開発者アプリケーションにIdeogram生成の出力であることを明示し(たとえば、「Powered by Ideogram」)、アクセスが有効になっている場合は承認済みのIdeogramマークを表示し、ユーザーを利用ポリシーにリンクすることが求められています。AI出力を完全に人間が作成したもの、または実際の出来事を撮影した本物の写真として提示することは禁止されています。プロバイダー、エンドポイント/モデルのルート、UTC時刻、リクエストパラメーター、返されたプロンプト/シード/解像度、ソースのハッシュ/権利、出力のハッシュ、安全性の判定、人的承認を含む来歴記録を保持してください。現在のレスポンススキーマにはC2PAフィールドもダウンロード可能な来歴マニフェストも記載されていないため、存在すると主張してはいけません。

APIの入力/出力はIdeogramモデルのトレーニングには使用されません。ただし、利用ポリシー違反の疑いがあるものとしてフラグが付いたコンテンツは、不正利用や安全性の検出をトレーニング/改善するために使用される場合があります。より広範なプライバシーポリシーでは、プロンプト/アップロードが収集されることと、目的に基づく数値で定められていない保持期間(「合理的に必要な期間」)が示されています。また、サービスプロバイダーの利用と国際的なデータ処理も認められています。そのため、法的根拠を確認し、必要な同意を得ていない状態で、健康データ、子どものデータ、秘密情報、または機微な個人データを送信しないでください。注文ごとの契約で定められていない限り、データを一切保持しないことや、固定の削除期間を約束してはいけません。

enable_copyright_detection=trueを指定すると、組織設定ですでに有効になっている場合を除き、V3/V4リクエストで追加のHiveによる肖像/ロゴチェックが有効になります。処理時間が長くなり、フラグが付いた画像はis_image_safe:falseを返します。これは追加の判定材料として扱い、法的な利用許可とはみなさないでください。

QA を実行し、安全に移行する

送信前:

  1. リリース自動化で、ルート、サイズ、プリセットの OpenAPI 列挙型を再取得します。
  2. ファイルの種類/サイズ、マスクの寸法、枚数、シード、相互排他的なフィールド、V4 の bbox の順序と範囲を検証します。
  3. コスト、参照画像/類似性、公開配布、ポリシー上慎重な扱いが必要なプロンプトについて、承認を必須にします。
  4. 送信前に、機密情報を伏せた正規リクエストとソースハッシュを保存します。

受信後:

  1. URL の欠落や安全でない結果を拒否し、一時 URL は直ちにダウンロードします。
  2. MIME シグネチャ、ピクセル寸法、要求された場合のアルファチャンネル、暗号学的ハッシュを検証します。
  3. OCR と人によるレビューで文字列を照合し、完全な忠実度を前提とせずに参照画像の人物の同一性/スタイルを比較します。
  4. 編集では保護領域の差分を確認し、インペイントではマスクの継ぎ目を確認し、リフレームでは被写体の保持と新しい境界を確認し、背景処理では髪や透明部分のハローを確認します。
  5. エンドポイント、返されたプロンプト、シード、実際の解像度、価格の算定基準、レビュー結果を記録します。

移行マップ:

  • /v1/ideogram-v3/edit と /edit のマスクワークフローを /v1/ideogram-v3/inpaint に移行します。image_file を image に変更し、編集可能領域には黒を使用します。
  • 新しいテキスト/レイアウト作業には旧 /generate を V4 Generate に移行し、V3 の参照画像/プリセット/カスタムモデルが必要な場合は V3 Generate に移行します。
  • 必要な制御項目に応じて、旧 /remix と /reframe を /v1/ideogram-v4/remix または V3 の正規ルートに移行します。
  • 旧 model/magic_prompt_option/接頭辞付きのアスペクト比列挙型を、ルート固有のモデルと現在の正確なフィールド名に置き換えます。
  • プロンプト/アセットを固定して各移行をカナリアテストし、寸法、文字列、スタイル、被写体の保持、安全性に関する挙動、レイテンシ、コストを比較します。モデル世代をまたいでシードが同等だと決して想定しないでください。

既知のファーストパーティの曖昧さ

  • カスタムモデルのチュートリアルではデータセット画像が最低 10 枚とされていますが、現在の train ルート/OpenAPI コントラクトでは 15~100 枚とされています。より厳しい 15 枚の最小要件を適用し、有料トレーニングの実行直前に再確認してください。
  • OpenAPI の WebhookUrl コンポーネントでは、webhook 本文は request_id を含む同期レスポンスを反映するとされていますが、非同期エンドポイントのスキーマと専用の webhook ガイドでは generation_id が使われています。専用ガイド/ヘッダーのコントラクトに従い、一致する generation_id を必須にしてください。不一致が解消されるまでは、request_id に関する記述を古い情報として扱います。
  • Layerize Text の説明文と例では、フォント/位置/スタイルのデータを含む text_blocks が示されていますが、現在の OpenAPI LayerizeTextResponse スキーマで宣言されているのは base_image_url、任意の original_image_url、および seed のみです。宣言されたスキーマに従って実装し、text_blocks は機能検出してください。Ideogram が不一致を解消するまでは必須にしないでください。
  • API 概要の V3 クイックスタートでは、参照/OpenAPI がマルチパートフォームを宣言しているルートに対して JSON 本文が示されています。特にファイルを送信する場合は、ルートの参照/OpenAPI に従ってください。
  • V4 のレスポンス例には複数のデータ項目が示されていますが、リクエストスキーマには num_images がありません。例に示された件数を保証として扱わないでください。
  • 価格情報では複数の処理クラスにわたる V4 の価格が説明されていますが、現在の OpenAPI では V4 Generate/Remix が公開されている一方、インペイント/リフレーム/背景置換には V3 専用パスが残っています。価格表の記述ではなく、文書化されたルートの利用可否に基づいて選択してください。

公式の情報源

コメント