Image Generation

The image generation endpoints support both OpenAI POST /v1/images/generations and Gemini Imagen POST /v1beta/models/{model}:predict formats. Return single or multiple images as URLs or base64-encoded data.

POST /v1/images/variations is not in that list — OpenAI removed the endpoint, and aimock mocks it as removed. See Variations: mocked as removed.

Endpoints

Method Path Format
POST /v1/images/generations JSON (OpenAI)
POST /v1beta/models/{model}:predict JSON (Gemini Imagen)

OpenAI image generation and Gemini Imagen requests accept a string or a nonempty array of content parts for prompt. Each part must be an object with a string type. If a part includes text, that value must be a string. Mixed parts, nontext parts, and empty text remain accepted. Unsupported values return HTTP 400 before fixture matching and do not consume a fixture sequence position.

Unit Test: Single Image URL

Using the programmatic API with vitest, register a fixture and assert on the response.

image-url.test.ts ts
import { LLMock } from "@copilotkit/aimock";
import { describe, it, expect, beforeAll, afterAll } from "vitest";

let mock: LLMock;

beforeAll(async () => {
  mock = new LLMock();
  await mock.start();
});

afterAll(async () => {
  await mock.stop();
});

it("returns a single image URL", async () => {
  mock.onImage("a sunset over mountains", {
    image: { url: "https://example.com/sunset.png" },
  });

  const res = await fetch(`${mock.url}/v1/images/generations`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "dall-e-3",
      prompt: "a sunset over mountains",
      n: 1,
      size: "1024x1024",
    }),
  });

  const body = await res.json();
  expect(body.data[0].url).toBe("https://example.com/sunset.png");
});

Unit Test: Multiple Images

image-multiple.test.ts ts
it("returns multiple images", async () => {
  mock.onImage("cats", {
    images: [
      { url: "https://example.com/cat1.png" },
      { url: "https://example.com/cat2.png" },
    ],
  });

  const res = await fetch(`${mock.url}/v1/images/generations`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "dall-e-3",
      prompt: "cats playing",
      n: 2,
    }),
  });

  const body = await res.json();
  expect(body.data).toHaveLength(2);
  expect(body.data[0].url).toBe("https://example.com/cat1.png");
  expect(body.data[1].url).toBe("https://example.com/cat2.png");
});

Unit Test: Base64 Response

image-base64.test.ts ts
it("returns base64-encoded image", async () => {
  mock.onImage("logo", {
    image: { b64_json: "iVBORw0KGgoAAAANSUhEUg..." },
  });

  const res = await fetch(`${mock.url}/v1/images/generations`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "dall-e-3",
      prompt: "a company logo",
      response_format: "b64_json",
    }),
  });

  const body = await res.json();
  expect(body.data[0].b64_json).toBeDefined();
});

Unit Test: Gemini Imagen Format

image-gemini.test.ts ts
it("handles Gemini Imagen predict endpoint", async () => {
  mock.onImage("landscape", {
    image: { url: "https://example.com/landscape.png" },
  });

  const res = await fetch(
    `${mock.url}/v1beta/models/imagen-3.0-generate-002:predict`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        instances: [{ prompt: "a beautiful landscape" }],
        parameters: { sampleCount: 1 },
      }),
    }
  );

  const body = await res.json();
  expect(body.predictions).toBeDefined();
});

JSON Fixture

fixtures/images.json json
{
  "fixtures": [
    {
      "match": { "userMessage": "sunset" },
      "response": {
        "image": { "url": "https://example.com/sunset.png" }
      }
    },
    {
      "match": { "userMessage": "cats" },
      "response": {
        "images": [
          { "url": "https://example.com/cat1.png" },
          { "url": "https://example.com/cat2.png" }
        ]
      }
    }
  ]
}

Response Format

Matches the OpenAI /v1/images/generations response format:

Image fixtures use match.userMessage which maps to the prompt field in the request body. The prompt matcher checks for substring matches.

Variations: mocked as removed

POST /v1/images/variations no longer exists upstream. It only ever served dall-e-2, which OpenAI removed on 2026-05-12, and the endpoint went with it. aimock answers the path with the removal the real API returns today rather than a plausible image envelope, so the removal surfaces in your tests instead of in production. This follows aimock’s deprecated & removed APIs policy — a removed surface is mocked as removed, never as a success.

A live request to https://api.openai.com/v1/images/variations returns a bare 404 with a zero-byte body and no Content-Type — not a JSON error envelope. The 404 arrives before authentication (a keyless call to /v1/images/generations still returns 401), which is how you can tell the path itself is gone rather than the model being rejected. aimock replays exactly that.

Variations request → removal 404 sh
curl -i -X POST $MOCK_URL/v1/images/variations -F [email protected]

HTTP/1.1 404 Not Found
<no Content-Type, empty body>

Tests that drove variations should move to POST /v1/images/edits, which OpenAI still serves and aimock still mocks normally — along with POST /v1/images/generations. Only variations is affected. The endpoint is also excluded from aimock’s drift pipeline, because there is no live endpoint left to compare against.

Record & Replay

When no fixture matches an incoming request, aimock can proxy it to the real API and record the response as a fixture for future replays. Enable recording with the --record flag or via RecordConfig in the programmatic API. Recorded image fixtures capture the url or b64_json from the provider response and save them to disk, so subsequent runs replay instantly without hitting the real API.

CLI sh
npx -p @copilotkit/aimock llmock --record --provider-openai https://api.openai.com