広告枠を募集中 ご相談ください

Getimg.ai REST APIを本番環境へ統合する:同時実行制御、レートリミット、カスタムモデルエンドポイントの実装

365Finds Editorial
Wiki 記事 11,430 本収録

あなたの AI で 365 Wiki を使う

下のプロンプトをコピーして、普段使っている AI に貼り付け、そのまま質問してください。

完全版 Skill をダウンロード

まずはプロンプトでお試しください。よく使う場合は完全版 365Wiki Skill をインストールすると、毎回貼り付ける必要がありません。普段使う AI がなければ、Gemini や Microsoft Copilot のウェブ版でも使えます。

Webアプリケーションやモバイルアプリに高品質な画像・動画生成AIを組み込む際、自前でGPUクラスタを構築・運用することは極めて大きなコストと運用負荷を伴います。GPUインスタンスのコールドスタート、スパイクするトラフィック需要への追従、VRAM容量の制約、ドライバや推論フレームワークの保守など課題は山積しています。Getimg.aiのようなマネージド推論プラットフォームは、統一されたREST API経由でFLUXシリーズ、Stable Diffusion XL(SDXL)、およびElementsによるカスタムスタイルの一貫性保持機能を提供し、ベアメタルGPUの調達や管理から開発者を解放します。

しかし、プロトタイプの実装から多数のユーザーがアクセスする本番システムへ移行するには、プラットフォーム固有の制限に適切に対処しなければなりません。分単位リクエスト数(RPM)制限、同時実行リクエスト数の上限、同期的な画像生成と非同期な動画生成のライフサイクルの違い、そして一時的なメディア保持期間などを考慮したアーキテクチャ設計が不可欠です。

本ガイドでは、Getimg.ai REST APIの仕様を詳細に解説し、レートリミット制御、耐障害性の高いキュー設計、モデルパラメータの事前検証、生成アセットの永続化に至る本番バックエンドの構築手法を解説します。


Getimg.ai APIの基本アーキテクチャ

Getimg.aiは https://api.getimg.ai/v2 をベースURLとする統一REST APIを採用しています。すべてのリクエストは、標準的なHTTP Authorization ヘッダーにBearerトークン形式でAPIキーを含める必要があります。

Authorization: Bearer sk_your_api_key_here
Content-Type: application/json

同期的な画像生成と非同期な動画生成

Getimg.aiは、メディア生成のレイテンシ特性に応じて2つの異なる実行モデルを提供しています。

  1. 画像生成(POST /v2/images/generations):同期的に動作します。推論処理が完了するまでHTTP接続が維持され、生成メタデータと署名付きダウンロードURLを含む HTTP 200 レスポンスが即座に返されます。
  2. 動画生成(POST /v2/videos/generations):非同期に動作します。リクエストを送信すると HTTP 202 Accepted とタスクID(req-...)が即座に返されます。ワーカープロセスは GET /v2/videos/generations/{id} を定期的にポーリングし、ステータスが completed または failed に遷移するのを待機する必要があります。
[クライアントアプリケーション]
         │
         ├─── (同期 画像生成) ───> POST /v2/images/generations ───> HTTP 200 [完了 + 署名付きURL]
         │
         └─── (非同期 動画生成) ──> POST /v2/videos/generations ───> HTTP 202 [保留タスクID]
                                                │
                                                ▼ (ポーリングループ)
                                   GET /v2/videos/generations/{id} ─> HTTP 200 [状態: completed / failed]

24時間のエフェメラルストレージポリシー

生成されたメディアファイルは、Getimg.aiのエッジサーバー上で24時間のみ保持されます。レスポンスの各データ項目にはISO 8601形式の deletes_at タイムスタンプが含まれます。本番環境のシステムは、この有効期限が切れる前に生成ファイルをプライベートクラウドストレージ(Amazon S3、Google Cloud Storage、Cloudflare R2など)にストリーミング転送して永続化する必要があります。


レートリミットと同時実行制御のメカニズム

Getimg.aiはワークスペース単位で3つの独立した制限を適用しています。

制限項目対象エンドポイント判定ウィンドウ適用ルール
画像 RPMPOST /v2/images/generations60秒ローリング/固定画像生成の送信時にカウント。1分あたりの最大リクエスト数を制限
動画 RPMPOST /v2/videos/generations60秒ローリング/固定動画生成の送信時にカウント
同時実行数(Concurrent)画像および動画の全生成エンドポイント瞬時処理中(in-flight)画像・動画共通の枠。処理中の画像接続および保留中の動画タスクがスロットを消費

GET /v2/videos/generations/{id} などの読み取りエンドポイントはRPM枠を消費しません。ただし重要な点として、処理中の動画生成ジョブは、完了(completed)または失敗(failed)するまで同時実行スロットを消費し続けます。

レートリミットヘッダー

すべての生成レスポンスヘッダーには、利用状況を追跡するためのテレメトリ情報が含まれます。

RPMまたは同時実行数の上限を超過すると、APIは HTTP 429 Too Many Requests を返します。RPM制限によるエラーの場合、レスポンスには再試行までに待機すべき秒数を示す Retry-After ヘッダーが付与されます。

{
  "error": {
    "message": "Too many concurrent requests. Please wait for current requests to finish.",
    "type": "invalid_request_error",
    "param": null,
    "code": "rate_limit"
  }
}

エラー分類:リトライ可能エラーと恒久エラー

本番クラスタのクライアントは、一時的なスロットリングと修正が必要な不正リクエストを明確に区別してハンドリングする必要があります。

ステータスコードエラーコード (error.code)分類推奨される対応策
429rate_limitリトライ可能Retry-After の秒数待機、またはジッター付き指数バックオフで再試行
500server_errorリトライ可能サーキットブレーカーを適用した指数バックオフで再試行
400invalid_parameter, parameter_missing恒久エラー入力パラメータの誤り。モデル仕様と突き合わせて上流でバリデーションを実施
401invalid_api_key恒久エラーAPIキーが無効。運用チームへアラートを発報し認証設定を確認
402quota_exceeded恒久エラー残高不足。タスクの新規発行を一時停止しワークスペースに課金チャージ
404not_found恒久エラーモデルIDまたはタスクIDが存在しない。指定リソースを確認
422content_policy_violation恒久エラーセーフティフィルタに抵触。プロンプトや入力画像の見直しをエンドユーザーに通知

動的モデル探索とカスタムモデルエンドポイント

旧来のGetimg v1 APIでは、モデルごとに個別のURLパス(/v1/stable-diffusion-xl/text-to-image や /v1/flux-schnell/text-to-image など)が用意されていました。現行のv2規格では、FLUXシリーズ、SDXL、注目のコミュニティモデルなど、すべての基盤モデルが /v2/images/generations に集約され、リクエストボディ内の model フィールドによって動的に切り替えられます。

本番運用のための事前検証(Pre-flight Validation)

解像度やアスペクト比をクライアント側で固定値としてハードコードしていると、モデルのバージョンアップ時に予期せぬエラーが発生します。信頼性の高いシステムを構築するには、モデルカタログエンドポイントを定期的に参照します。

返却されるモデルメタデータの例:

{
  "id": "seedream-5-lite",
  "name": "Seedream 5 Lite",
  "type": "image",
  "supported_aspect_ratios": ["1:1", "16:9", "9:16", "2:3", "3:2"],
  "supported_resolutions": ["1K", "2K", "4K"],
  "supports_sound": false,
  "supported_references": {
    "reference_image": 14
  },
  "pricing": {
    "currency": "USD",
    "rates": [
      {
        "resolution": "2K",
        "sound": "not_applicable",
        "billable_unit": "image",
        "unit_price": "0.1400"
      }
    ]
  }
}

アプリケーション起動時にモデルメタデータを取得してメモリにキャッシュ(TTL 1時間など)しておくことで、課金リクエストをAPIに投げる前にローカルでパラメータ妥当性を検証し、無駄なHTTP 400エラーの発生を抑止できます。

スタイルとキャラクターの一貫性:Elementsとリファレンス画像

企業のブランディングや特定キャラクターの一貫した描画には、以下の機能を利用します。

  1. Elements構文(@ElementName):Getimg.aiでは、複雑なLoRAやDreamboothの自前ホスティングに代わり、Elements機能を採用しています。管理画面でElementを作成しておけば、プロンプト内で @ElementName を指定するだけで、基盤モデルがそのビジュアル概念を自動的に統合して生成します。
  2. マルチモーダルリファレンス入力(images 配列):画像誘導生成に対応したモデルでは、最大20枚のリファレンス画像を送信できます。画像は公開HTTPS URLまたはBase64データURL(data:image/png;base64,...)が使用可能です。デコード後の画像サイズは1枚あたり10 MiB以内、リクエスト全体のJSONサイズは25 MiB以内に収める必要があります。

本番向け高可用アーキテクチャ:同時実行とキュー制御

多数のユーザーからリクエストが集中する本番環境において、Webサーバーから直接外部APIを同期呼び出しすると、瞬時にレート制限に到達しシステム障害につながります。RedisとBullMQ(Node.js)やCelery(Python)を組み合わせた分散ジョブキューによる非同期ワーカー構成が推奨されます。

[Webアプリケーションクラスタ]
             │
             ▼
[Redis ジョブキュー (BullMQ)]
             │
  ┌──────────┴────────────────────────┐
  ▼                                   ▼
[Worker ノード 1]               [Worker ノード 2]
  │ セマフォ & トークンバケット       │ セマフォ & トークンバケット
  └──────────┬────────────────────────┘
             │
             ▼
[Getimg.ai REST API v2] (レートリミット & 同時実行制御)
             │
             ▼ (HTTP 200: data[0].url)
[アセット永続化ワーカー] ───────> [プライベート S3 / R2 ストレージ]

アーキテクチャの必須要素

  1. 分散セマフォ(同時実行数制御):ワーカー全体で実行中の推論ジョブ数を管理し、アカウントの上限スロットを超えないよう制御します。
  2. トークンバケットレートリミッター(RPM制御):リクエスト送信ペースを平滑化し、RPM上限の超過を回避します。
  3. フルジッター付き指数バックオフ(Jittered Backoff):429や500エラーを受信した場合、Retry-After ヘッダーを尊重しつつランダムなジッターを加えて再試行し、リトライ集中(Thundering Herd)を防ぎます。
  4. 即時永続化パイプライン:生成完了直後に画像バイナリを自社のS3ストレージに保存し、データベースのURLを更新します。

実装例:本番用クライントとキューワーカー(TypeScript)

公式の getimg-ai SDKとAxiosを組み合わせた、本番レベルのTypeScript実装例です。

1. サービスクライアントの実装

import GetimgAI from "getimg-ai";
import axios, { AxiosInstance } from "axios";

export interface ModelMetadata {
  id: string;
  name: string;
  type: "image" | "video";
  supported_aspect_ratios: string[];
  supported_resolutions: string[];
  supported_references: Record<string, number>;
  pricing: {
    currency: string;
    rates: Array<{ resolution: string; unit_price: string; billable_unit: string }>;
  };
}

export class ProductionGetimgService {
  private client: GetimgAI;
  private http: AxiosInstance;
  private modelCache: Map<string, { data: ModelMetadata; expiresAt: number }> = new Map();

  constructor(private apiKey: string, private baseUrl: string = "https://api.getimg.ai") {
    this.client = new GetimgAI({ apiKey, maxRetries: 0 }); // リトライは上位のキューで制御
    this.http = axios.create({
      baseURL: this.baseUrl,
      headers: {
        Authorization: `Bearer ${this.apiKey}`,
        "Content-Type": "application/json",
      },
      timeout: 60000,
    });
  }

  /**
   * 事前検証用にモデルメタデータを取得してキャッシュ
   */
  async getModelMetadata(modelId: string): Promise<ModelMetadata> {
    const cached = this.modelCache.get(modelId);
    if (cached && cached.expiresAt > Date.now()) {
      return cached.data;
    }

    try {
      const response = await this.http.get<ModelMetadata>(`/v2/models/${modelId}`);
      this.modelCache.set(modelId, {
        data: response.data,
        expiresAt: Date.now() + 1000 * 60 * 60, // 1時間キャッシュ
      });
      return response.data;
    } catch (error: any) {
      if (error.response?.status === 404) {
        throw new Error(`モデル '${modelId}' はGetimg.ai上に存在しません`);
      }
      throw error;
    }
  }

  /**
   * 生成パラメータの事前検証
   */
  async validateGenerationPayload(
    modelId: string,
    aspectRatio?: string,
    resolution?: string
  ): Promise<void> {
    const meta = await this.getModelMetadata(modelId);

    if (aspectRatio && !meta.supported_aspect_ratios.includes(aspectRatio)) {
      throw new Error(
        `モデル ${modelId} はアスペクト比 '${aspectRatio}' をサポートしていません。利用可能: ${meta.supported_aspect_ratios.join(", ")}`
      );
    }

    if (resolution && !meta.supported_resolutions.includes(resolution)) {
      throw new Error(
        `モデル ${modelId} は解像度 '${resolution}' をサポートしていません。利用可能: ${meta.supported_resolutions.join(", ")}`
      );
    }
  }

  /**
   * ジッター付きバックオフを備えた画像生成処理
   */
  async generateImageWithRetry(params: {
    model: string;
    prompt: string;
    aspectRatio?: string;
    resolution?: string;
    outputFormat?: "jpeg" | "png" | "webp";
    images?: Array<{ url: string }>;
  }, maxAttempts: number = 4): Promise<{ permanentUrl: string; usage: any }> {
    await this.validateGenerationPayload(params.model, params.aspectRatio, params.resolution);

    let attempt = 0;
    while (attempt < maxAttempts) {
      attempt++;
      try {
        const result = await this.client.images.generate({
          model: params.model,
          prompt: params.prompt,
          aspect_ratio: params.aspectRatio,
          resolution: params.resolution,
          output_format: params.outputFormat ?? "jpeg",
          images: params.images,
        });

        const generatedItem = result.data[0];
        const permanentUrl = await this.persistToPermanentStorage(
          generatedItem.url,
          result.id,
          params.outputFormat ?? "jpeg"
        );

        return { permanentUrl, usage: result.usage };
      } catch (err: any) {
        const status = err.status || err.response?.status;
        const code = err.error?.code || err.response?.data?.error?.code;

        // 恒久エラーは即座に例外をスロー
        if (status === 400 || status === 401 || status === 402 || status === 422) {
          throw new Error(`Getimg.ai 恒久エラー [${status} - ${code}]: ${err.message}`);
        }

        if (attempt >= maxAttempts) {
          throw new Error(`最大再試行回数を超過しました。最後のエラー: ${err.message}`);
        }

        // Retry-Afterとジッターを加味した待機時間の計算
        let waitSeconds = Math.pow(2, attempt);
        const retryAfterHeader = err.headers?.["retry-after"] || err.response?.headers?.["retry-after"];
        if (retryAfterHeader) {
          waitSeconds = parseInt(retryAfterHeader, 10);
        }
        const jitter = Math.random() * 1.5;
        const totalDelayMs = (waitSeconds + jitter) * 1000;

        console.warn(`[Getimg] 一時的エラー (${status})。${totalDelayMs.toFixed(0)}ms 後に再試行します...`);
        await new Promise((resolve) => setTimeout(resolve, totalDelayMs));
      }
    }

    throw new Error("生成処理が予期せず終了しました。");
  }

  /**
   * 一時URLからメディアをダウンロードし社内S3へ永続化
   */
  private async persistToPermanentStorage(
    temporaryUrl: string,
    generationId: string,
    format: string
  ): Promise<string> {
    const downloadStream = await axios.get(temporaryUrl, { responseType: "arraybuffer" });
    const buffer = Buffer.from(downloadStream.data);

    // 本番環境のS3やCloudflare R2へのアップロード処理をここに実装
    const simulatedPermanentUrl = `https://cdn.yourapp.com/generations/${generationId}.${format}`;
    console.log(`${buffer.length} バイトを ${simulatedPermanentUrl} に保存しました`);
    return simulatedPermanentUrl;
  }
}

2. BullMQ キューワーカーの実装

import { Queue, Worker, Job } from "bullmq";
import Redis from "ioredis";
import { ProductionGetimgService } from "./ProductionGetimgService";

const connection = new Redis(process.env.REDIS_URL || "redis://127.0.0.1:6379");
const getimgService = new ProductionGetimgService(process.env.GETIMG_API_KEY!);

export interface ImageJobPayload {
  userId: string;
  model: string;
  prompt: string;
  aspectRatio: string;
  resolution: string;
}

// 共通ジョブキューの定義
export const imageQueue = new Queue<ImageJobPayload>("getimg-image-generation", {
  connection,
  defaultJobOptions: {
    attempts: 3,
    backoff: {
      type: "exponential",
      delay: 3000,
    },
    removeOnComplete: 1000,
    removeOnFail: 5000,
  },
});

// レート制限と同時実行制御を備えたワーカー
export const imageWorker = new Worker<ImageJobPayload>(
  "getimg-image-generation",
  async (job: Job<ImageJobPayload>) => {
    console.log(`タスク ${job.id} を処理中(ユーザー: ${job.data.userId})`);

    const result = await getimgService.generateImageWithRetry({
      model: job.data.model,
      prompt: job.data.prompt,
      aspectRatio: job.data.aspectRatio,
      resolution: job.data.resolution,
    });

    return result;
  },
  {
    connection,
    // アカウントの同時実行制限値に設定
    concurrency: 5,
    limiter: {
      max: 60, // 最大リクエスト数
      duration: 60000, // 60,000ms(画像RPM制限)
    },
  }
);

本番運用のベストプラクティス

  1. 動画ポーリングと同時実行スロットの分離:POST /v2/videos/generations で動画を生成する際、ジョブが完了するまで同時実行スロットが占有されます。ポーリング頻度は3〜5秒程度に設定し、不要な接続の浪費を防ぎます。
  2. コスト監査とクォータ監視:レスポンスに含まれる usage オブジェクト(total_cost、unit_price、quantity)を監査ログに記録し、定期的に GET /v2/billing/costs で実利用状況と照合することで、402残高不足によるシステム停止を未然に防ぎます。
  3. プロンプトの事前セーフティ判定:HTTP 422 content_policy_violation は、ポリシーに抵触した入力を示します。生成キューに投入する前段でLlama Guardなどの軽量なテキストモデレーションを実施することで、無駄なAPI呼び出しとリトライコストを削減できます。
  4. 障害時の動的モデルフォールバック:高負荷によって特定モデルのレイテンシが悪化した場合に備え、同一のリクエスト構造を保ったまま seedream-5-lite や高速なFLUXバリアントへ自動フォールバックする耐障害設計を組み込みます。

参考資料