Images API

The Images API's Generations and Edits Endpoints

The Images API is one request, one result. POST /v1/images/generations takes JSON with a prompt; POST /v1/images/edits also takes up to 16 input images and an optional mask (Masks and References), as files, URLs or file IDs.

first-image.mjs: one low-quality image from the Images APIJavaScript
import fs from 'node:fs';
import OpenAI from 'openai';
const openai = new OpenAI();
const res = await openai.images.generate({
  model: 'gpt-image-2.5-flare',
  prompt: 'A small independent bookshop window at dusk, six books on display, '
    + 'flat vector illustration, deep blue and saffron palette, no text',
  size: '1024x1024',
  quality: 'low',
});
const { data: [image], usage, ...meta } = res;
fs.writeFileSync('booknest-window.png', Buffer.from(image.b64_json, 'base64'));
console.log(Object.entries(meta).map(([k, v]) => `${k}=${v}`).join('  '));
console.log(`data[0].b64_json: ${image.b64_json.length} characters`);
console.log('usage:', usage);
Output
created=1790408640  background=opaque  output_format=png  quality=low  size=1024x1024
data[0].b64_json: 1680712 characters
usage: {
  input_tokens: 33,
  input_tokens_details: { image_tokens: 0, text_tokens: 33 },
  output_tokens: 196,
  output_tokens_details: { image_tokens: 196, text_tokens: 0 },
  total_tokens: 229
}

The call took 13 seconds and returned a 1.26 MB PNG. The usage object is the bill: 33 prompt tokens at $5 per million plus 196 image tokens at $30 per million, $0.0060. The image is on the left of the figure below.

One request, one result: an animated Images API call, its b64_json image and the bill from usageHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; background: #fafaf7; font: 12px system-ui, sans-serif; color: #263238; }
  canvas { display: block; max-width: 100%; }
</style>
<p>Endpoint: <label><input type="radio" name="e" value="gen" checked> POST /v1/images/generations</label>
  <label><input type="radio" name="e" value="edit"> POST /v1/images/edits</label> <button id="send">Send</button></p>
<canvas id="c" width="600" height="300"></canvas>
<script>
  const ctx = document.getElementById('c').getContext('2d');
  // A stand-in for the returned PNG: a bookshop window at dusk, painted in code
  function paintWindow(g, x, y, s, edited) {
    const sky = g.createLinearGradient(0, y, 0, y + s);
    sky.addColorStop(0, '#1f3f66');  sky.addColorStop(1, '#e09a10');
    g.fillStyle = sky;  g.fillRect(x, y, s, s);
    g.fillStyle = '#5b3f99';  g.fillRect(x + s * 0.12, y + s * 0.2, s * 0.76, s * 0.62);        // shop front
    g.fillStyle = '#fff3e0';  g.fillRect(x + s * 0.2, y + s * 0.3, s * 0.6, s * 0.36);          // window
    ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'].forEach((c, i) => {
      g.fillStyle = c;  g.fillRect(x + s * (0.24 + i * 0.09), y + s * 0.42, s * 0.06, s * 0.2);
    });
    g.fillStyle = '#6b4f3a';  g.fillRect(x, y + s * 0.82, s, s * 0.18);
    if (edited) {                                        // the edit: a cart of books bottom left
      g.fillStyle = '#8d6e63';  g.fillRect(x + s * 0.04, y + s * 0.74, s * 0.24, s * 0.1);
      g.fillStyle = '#e09a10';  g.fillRect(x + s * 0.06, y + s * 0.68, s * 0.2, s * 0.06);
    } else {
      g.fillStyle = '#3f7d3a';  g.beginPath();  g.arc(x + s * 0.12, y + s * 0.76, s * 0.07, 0, 7);  g.fill();
    }
  }
  const usage = { gen: { text: 33, imageIn: 0, out: 196 }, edit: { text: 45, imageIn: 1024, out: 196 } };
  const RATE = { text: 5, imageIn: 8, out: 30 };             // $ per million tokens
  let t = 0, mode = 'gen', anim;

  function box(x, y, w, h, title, lines, color) {
    ctx.fillStyle = '#fff';  ctx.strokeStyle = color;  ctx.lineWidth = 2;
    ctx.beginPath();  ctx.roundRect(x, y, w, h, 8);  ctx.fill();  ctx.stroke();
    ctx.fillStyle = color;  ctx.font = 'bold 12px system-ui';  ctx.fillText(title, x + 8, y + 18);
    ctx.fillStyle = '#263238';  ctx.font = '11px monospace';
    lines.forEach((l, i) => ctx.fillText(l, x + 8, y + 36 + i * 15));
  }
  function draw() {
    ctx.clearRect(0, 0, 600, 300);
    const req = mode === 'gen'
      ? ['model: gpt-image-2.5-flare', "prompt: 'A small bookshop…'", "size: '1024x1024'", "quality: 'low'"]
      : ['model: gpt-image-2.5-sunburst', 'image: window.png (≤ 16)', 'mask: window-mask.png', "prompt: 'Bottom left: a cart…'"];
    box(10, 20, 230, 110, 'request (JSON or multipart)', req, '#1f5f8b');
    box(360, 20, 230, 110, 'OpenAI', ['one call, one result', '~13 s', 'always b64_json'], '#5b3f99');
    // the packet travelling there and back
    const p = Math.min(1, t / 60), q = Math.max(0, Math.min(1, (t - 100) / 60));
    ctx.fillStyle = '#e09a10';
    if (t < 60) { ctx.beginPath();  ctx.arc(240 + p * 120, 75, 7, 0, 7);  ctx.fill(); }
    if (t >= 60 && t < 100) { ctx.fillStyle = '#5b3f99';  ctx.fillText('generating' + '.'.repeat(Math.floor(t / 10) % 4), 400, 120); }
    if (t >= 100 && t < 160) { ctx.fillStyle = '#3f7d3a';  ctx.beginPath();  ctx.arc(360 - q * 120, 75, 7, 0, 7);  ctx.fill(); }
    if (t >= 160) {
      paintWindow(ctx, 10, 150, 140, mode === 'edit');
      const u = usage[mode], cost = (u.text * RATE.text + u.imageIn * RATE.imageIn + u.out * RATE.out) / 1e6;
      ctx.fillStyle = '#263238';  ctx.font = '12px monospace';
      [`data[0].b64_json → Buffer → ${mode === 'gen' ? 'booknest-window.png' : 'window-edited.png'}`,
       `usage: ${u.text} text in × $5/M`, `     + ${u.imageIn} image in × $8/M`, `     + ${u.out} image out × $30/M`,
       `     = $${cost.toFixed(4)}`].forEach((l, i) => ctx.fillText(l, 165, 170 + i * 20));
    }
    if (t < 170) { t++;  anim = requestAnimationFrame(draw); }
  }
  function send() { cancelAnimationFrame(anim);  t = 0;  draw(); }
  document.querySelectorAll('input[name=e]').forEach(r => r.onchange = () => { mode = r.value;  send(); });
  document.getElementById('send').onclick = send;
  t = 170;  draw();                                        // start by showing a finished call
</script>