Imagen API docs
Navigation menu
Reference

Smart Editing (I2I)

Smart Editing uses Imagen’s image-to-image model. No AI Profile is required. The endpoint surface lives under /v1/i2i/projects/... and parallels the profile-based flow, but the edit body is much smaller and the output JPEGs are ready as soon as the project reports Completed. There is no separate export step.

Shooting real estate?

This page is the endpoint reference. For when to use Smart Editing versus the profile-based flow, the built-in presets, and genre-specific guidance, see the Real estate guide.

Flow overview

  1. POST /v1/i2i/projects - create the project.
  2. POST /v1/i2i/projects/{uuid}/get_temporary_upload_links - presigned S3 PUTs.
  3. PUT <upload_link> with an empty Content-Type - upload image bytes.
  4. POST /v1/i2i/projects/{uuid}/edit - run the smart edit.
  5. GET /v1/i2i/projects/{uuid} - poll until status is Completed.
  6. GET /v1/i2i/projects/{uuid}/get_temporary_download_links - download JPEGs.

1. Create the project

POST /v1/i2i/projects

Names must be unique per account. If you omit name or send an empty body, the server auto-generates one for you.

curl -X POST 'https://api.imagen-ai.com/v1/i2i/projects' \
  --header 'x-api-key: $IMAGEN_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"name": "Real Estate Listing 1734567890"}'
# The Imagen SDK does not cover the I2I surface — use httpx directly.
import httpx, os

async with httpx.AsyncClient() as http:
    resp = await http.post(
        "https://api.imagen-ai.com/v1/i2i/projects",
        headers={"x-api-key": os.environ["IMAGEN_API_KEY"]},
        json={"name": "Real Estate Listing 1734567890"},
    )
    project_uuid = resp.json()["data"]["project_uuid"]
const res = await fetch('https://api.imagen-ai.com/v1/i2i/projects', {
  method: 'POST',
  headers: {
    'x-api-key': process.env.IMAGEN_API_KEY!,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: 'Real Estate Listing 1734567890' }),
});
const { data } = (await res.json()) as { data: { project_uuid: string } };
import (
    "bytes"
    "encoding/json"
    "net/http"
    "os"
)

body, _ := json.Marshal(map[string]any{"name": "Real Estate Listing 1734567890"})
req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
    "https://api.imagen-ai.com/v1/i2i/projects",
    bytes.NewReader(body),
)
req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
import java.net.URI;
import java.net.http.*;

var body = """
    {"name": "Real Estate Listing 1734567890"}
    """;
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.imagen-ai.com/v1/i2i/projects"))
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects")
req = Net::HTTP::Post.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']
req['Content-Type'] = 'application/json'
req.body = { name: 'Real Estate Listing 1734567890' }.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
project_uuid = JSON.parse(res.body)['data']['project_uuid']
<?php
$ch = curl_init('https://api.imagen-ai.com/v1/i2i/projects');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'x-api-key: ' . getenv('IMAGEN_API_KEY'),
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode(['name' => 'Real Estate Listing 1734567890']),
]);
$response = curl_exec($ch);
curl_close($ch);
$project_uuid = json_decode($response, true)['data']['project_uuid'];
using System.Net.Http;
using System.Text;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var payload = JsonSerializer.Serialize(new { name = "Real Estate Listing 1734567890" });
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync(
    "https://api.imagen-ai.com/v1/i2i/projects", content);
var json = await response.Content.ReadAsStringAsync();
{ "data": { "project_uuid": "65f3a1c2d4e5f6a7b8c9d0e1" } }

Save the project_uuid. Every subsequent call uses it.

Check a name before creating

GET /v1/i2i/projects/is_valid_name?name=NAME
curl 'https://api.imagen-ai.com/v1/i2i/projects/is_valid_name?name=$NAME' \
  --header 'x-api-key: $IMAGEN_API_KEY'
import httpx, os

async with httpx.AsyncClient() as http:
    resp = await http.get(
        "https://api.imagen-ai.com/v1/i2i/projects/is_valid_name",
        params={"name": name},
        headers={"x-api-key": os.environ["IMAGEN_API_KEY"]},
    )
    is_valid = resp.json()["data"]["is_valid"]
const url = new URL('https://api.imagen-ai.com/v1/i2i/projects/is_valid_name');
url.searchParams.set('name', name);
const res = await fetch(url, {
  headers: { 'x-api-key': process.env.IMAGEN_API_KEY! },
});
const { data } = (await res.json()) as { data: { is_valid: boolean } };
import (
    "encoding/json"
    "net/http"
    "net/url"
    "os"
)

u := "https://api.imagen-ai.com/v1/i2i/projects/is_valid_name?name=" + url.QueryEscape(name)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var result struct {
    Data struct {
        IsValid bool `json:"is_valid"`
    } `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&result)
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;

var uri = URI.create("https://api.imagen-ai.com/v1/i2i/projects/is_valid_name?name="
    + URLEncoder.encode(name, StandardCharsets.UTF_8));
var request = HttpRequest.newBuilder()
    .uri(uri)
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .GET()
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects/is_valid_name")
uri.query = URI.encode_www_form(name: name)
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
is_valid = JSON.parse(res.body)['data']['is_valid']
<?php
$url = 'https://api.imagen-ai.com/v1/i2i/projects/is_valid_name?'
  . http_build_query(['name' => $name]);
$ch = curl_init($url);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
]);
$response = curl_exec($ch);
curl_close($ch);
$is_valid = json_decode($response, true)['data']['is_valid'];
using System.Net.Http;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var response = await client.GetAsync(
    $"https://api.imagen-ai.com/v1/i2i/projects/is_valid_name?name={Uri.EscapeDataString(name)}");
var json = await response.Content.ReadAsStringAsync();
{ "data": { "is_valid": true } }
POST /v1/i2i/projects/PROJECT_UUID/get_temporary_upload_links

The request and response shape is the same as the profile-based flow: one file_name per file, and filenames must be unique. See Uploading for the full request body, the unique-filenames rule, and multipart upload for large files.

3. Upload the files

PUT each file to its upload_link with an explicit empty Content-Type header and no x-api-key. The mechanics are identical to the Quickstart flow and are documented once on the Uploading page.

4. Trigger Smart Editing

The edit body is much smaller than the profile-based flow: no profile_key, no photography_type, no export.

POST /v1/i2i/projects/PROJECT_UUID/edit
curl -X POST 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID/edit' \
  --header 'x-api-key: $IMAGEN_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "hdr_merge": true,
    "perspective_correction": true,
    "sky_replacement": true
  }'
import httpx, os

body = {
    "hdr_merge": True,
    "perspective_correction": True,
    "sky_replacement": True,
}
async with httpx.AsyncClient() as http:
    resp = await http.post(
        f"https://api.imagen-ai.com/v1/i2i/projects/{project_uuid}/edit",
        headers={"x-api-key": os.environ["IMAGEN_API_KEY"]},
        json=body,
    )
    print(resp.json()["data"]["message"])
const body = {
  hdr_merge: true,
  perspective_correction: true,
  sky_replacement: true,
};
const res = await fetch(
  `https://api.imagen-ai.com/v1/i2i/projects/${projectUuid}/edit`,
  {
    method: 'POST',
    headers: {
      'x-api-key': process.env.IMAGEN_API_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  }
);
import (
    "bytes"
    "encoding/json"
    "net/http"
    "os"
)

body, _ := json.Marshal(map[string]any{
    "hdr_merge": true,
    "perspective_correction": true,
    "sky_replacement": true,
})
req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
    "https://api.imagen-ai.com/v1/i2i/projects/"+projectUUID+"/edit",
    bytes.NewReader(body),
)
req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
import java.net.URI;
import java.net.http.*;

var body = """
    {
        "hdr_merge": true,
        "perspective_correction": true,
        "sky_replacement": true
    }
    """;
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.imagen-ai.com/v1/i2i/projects/" + projectUuid + "/edit"))
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects/#{project_uuid}/edit")
req = Net::HTTP::Post.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']
req['Content-Type'] = 'application/json'
req.body = {
  hdr_merge: true,
  perspective_correction: true,
  sky_replacement: true,
}.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
<?php
$ch = curl_init('https://api.imagen-ai.com/v1/i2i/projects/' . $projectUuid . '/edit');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'x-api-key: ' . getenv('IMAGEN_API_KEY'),
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'hdr_merge' => true,
    'perspective_correction' => true,
    'sky_replacement' => true,
  ]),
]);
$response = curl_exec($ch);
curl_close($ch);
using System.Net.Http;
using System.Text;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var payload = JsonSerializer.Serialize(new {
    hdr_merge = true,
    perspective_correction = true,
    sky_replacement = true,
});
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync(
    $"https://api.imagen-ai.com/v1/i2i/projects/{projectUuid}/edit", content);
{ "data": { "message": "Project ... sent for I2I editing successfully." } }

Fields:

  • hdr_merge: Merges bracketed exposures before editing.
  • perspective_correction: Straightens vertical and horizontal lines. Recommended for all interiors.
  • sky_replacement: Replaces blown-out skies with your account’s default sky template. Use it for exteriors.
  • callback_url: An optional HTTPS endpoint Imagen will POST to when editing finishes. See Callbacks.
HDR output is downsized in Smart Editing

When hdr_merge=true, brackets are merged and resized before editing (the long edge is bounded for the I2I model), so the final JPEGs are not full resolution. If you need full-resolution HDR output, use the profile-based flow instead.

/edit is idempotent

Calling /edit twice on the same project returns the same success message and won’t trigger a second edit. It is safe to retry on transient failures.

5. Poll status

Smart Editing uses a single status. There are no separate edit and export steps. Fetch the project and read its status. Pass get_thumbnail=false for a faster polling response. The server skips fetching the thumbnail, so the response returns sooner.

GET /v1/i2i/projects/PROJECT_UUID?get_thumbnail=false
# get_thumbnail=false skips the thumbnail for a faster polling response
curl 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID?get_thumbnail=false' \
  --header 'x-api-key: $IMAGEN_API_KEY'
import asyncio, httpx, os

async def wait_for_i2i(project_uuid: str) -> None:
    async with httpx.AsyncClient() as http:
        while True:
            resp = await http.get(
                f"https://api.imagen-ai.com/v1/i2i/projects/{project_uuid}",
                params={"get_thumbnail": "false"},
                headers={"x-api-key": os.environ["IMAGEN_API_KEY"]},
            )
            status = resp.json()["data"]["status"]
            if status == "Completed":
                return
            if status == "Failed":
                raise RuntimeError("Edit failed — contact support")
            await asyncio.sleep(30)
async function waitForI2I(projectUuid: string): Promise<void> {
  while (true) {
    const res = await fetch(
      `https://api.imagen-ai.com/v1/i2i/projects/${projectUuid}?get_thumbnail=false`,
      { headers: { 'x-api-key': process.env.IMAGEN_API_KEY! } }
    );
    const { data } = (await res.json()) as { data: { status: string } };
    if (data.status === 'Completed') return;
    if (data.status === 'Failed') throw new Error('Edit failed — contact support');
    await new Promise(r => setTimeout(r, 30_000));
  }
}
import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "time"
)

for {
    req, _ := http.NewRequestWithContext(ctx, http.MethodGet,
        "https://api.imagen-ai.com/v1/i2i/projects/"+projectUUID+"?get_thumbnail=false", nil)
    req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))

    resp, _ := http.DefaultClient.Do(req)
    var result struct {
        Data struct{ Status string `json:"status"` } `json:"data"`
    }
    json.NewDecoder(resp.Body).Decode(&result)
    resp.Body.Close()

    switch result.Data.Status {
    case "Completed":
        return nil
    case "Failed":
        return fmt.Errorf("edit failed — contact support")
    }
    time.Sleep(30 * time.Second)
}
import java.net.URI;
import java.net.http.*;

while (true) {
    var request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.imagen-ai.com/v1/i2i/projects/" + projectUuid + "?get_thumbnail=false"))
        .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
        .GET()
        .build();
    var response = HttpClient.newHttpClient()
        .send(request, HttpResponse.BodyHandlers.ofString());
    String status = parseStatus(response.body());
    if ("Completed".equals(status)) break;
    if ("Failed".equals(status)) throw new RuntimeException("Edit failed — contact support");
    Thread.sleep(30_000);
}
require 'net/http'
require 'json'
require 'uri'

loop do
  uri = URI("https://api.imagen-ai.com/v1/i2i/projects/#{project_uuid}?get_thumbnail=false")
  req = Net::HTTP::Get.new(uri)
  req['x-api-key'] = ENV['IMAGEN_API_KEY']

  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  status = JSON.parse(res.body)['data']['status']

  break if status == 'Completed'
  raise 'Edit failed — contact support' if status == 'Failed'
  sleep 30
end
<?php
while (true) {
  $ch = curl_init('https://api.imagen-ai.com/v1/i2i/projects/' . $projectUuid . '?get_thumbnail=false');
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
  ]);
  $status = json_decode(curl_exec($ch), true)['data']['status'];
  curl_close($ch);

  if ($status === 'Completed') break;
  if ($status === 'Failed') throw new RuntimeException('Edit failed — contact support');
  sleep(30);
}
using System.Net.Http;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

while (true) {
    var response = await client.GetAsync(
        $"https://api.imagen-ai.com/v1/i2i/projects/{projectUuid}?get_thumbnail=false");
    var json = await response.Content.ReadAsStringAsync();
    var status = JsonDocument.Parse(json)
        .RootElement.GetProperty("data").GetProperty("status").GetString();

    if (status == "Completed") break;
    if (status == "Failed") throw new Exception("Edit failed — contact support");
    await Task.Delay(TimeSpan.FromSeconds(30));
}
{
  "data": {
    "project_id": 42,
    "project_uuid": "65f3a1c2d4e5f6a7b8c9d0e1",
    "name": "Real Estate Listing 1734567890",
    "status": "In Progress",
    "created_at": "2026-05-31T10:42:11Z",
    "number_of_images": 42,
    "profile": null,
    "ai_tools": ["hdr_merge", "perspective_correction", "sky_replacement"],
    "customer_reference_id": 1234,
    "thumbnail_src": null,
    "export_status": null
  }
}

The possible statuses are Pending, In Progress, Completed, and Failed. Poll every 30 seconds, or skip polling entirely by passing a callback_url on /edit.

6. Download the edited JPEGs

Once status is Completed, download all output files in one call. Unlike the profile-based flow, this endpoint does not paginate. Every output file is returned in a single response.

GET /v1/i2i/projects/PROJECT_UUID/get_temporary_download_links
curl 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID/get_temporary_download_links' \
  --header 'x-api-key: $IMAGEN_API_KEY'
import httpx, os

async with httpx.AsyncClient() as http:
    resp = await http.get(
        f"https://api.imagen-ai.com/v1/i2i/projects/{project_uuid}/get_temporary_download_links",
        headers={"x-api-key": os.environ["IMAGEN_API_KEY"]},
    )
    files = resp.json()["data"]["files_list"]
    # files: [{"file_name": "...", "download_link": "..."}, ...]
const res = await fetch(
  `https://api.imagen-ai.com/v1/i2i/projects/${projectUuid}/get_temporary_download_links`,
  { headers: { 'x-api-key': process.env.IMAGEN_API_KEY! } }
);
const { data } = (await res.json()) as { data: { files_list: { file_name: string; download_link: string }[] } };
import (
    "encoding/json"
    "net/http"
    "os"
)

req, _ := http.NewRequestWithContext(ctx, http.MethodGet,
    "https://api.imagen-ai.com/v1/i2i/projects/"+projectUUID+"/get_temporary_download_links", nil)
req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var result struct {
    Data struct {
        FilesList []struct {
            FileName     string `json:"file_name"`
            DownloadLink string `json:"download_link"`
        } `json:"files_list"`
    } `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&result)
import java.net.URI;
import java.net.http.*;

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.imagen-ai.com/v1/i2i/projects/" + projectUuid + "/get_temporary_download_links"))
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .GET()
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
// Parse response.body() to read data.files_list
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects/#{project_uuid}/get_temporary_download_links")
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
files = JSON.parse(res.body)['data']['files_list']
<?php
$ch = curl_init('https://api.imagen-ai.com/v1/i2i/projects/' . $projectUuid . '/get_temporary_download_links');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
]);
$response = curl_exec($ch);
curl_close($ch);
$files = json_decode($response, true)['data']['files_list'];
using System.Net.Http;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var response = await client.GetAsync(
    $"https://api.imagen-ai.com/v1/i2i/projects/{projectUuid}/get_temporary_download_links");
var json = await response.Content.ReadAsStringAsync();
// Parse json to read data.files_list
{
  "data": {
    "files_list": [
      { "file_name": "DSC_0001.JPEG", "download_link": "https://<s3-presigned>" }
    ]
  }
}

Grab a single file by name

GET /v1/i2i/projects/PROJECT_UUID/get_download_link?file_name=DSC_0001.JPEG
curl 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID/get_download_link?file_name=DSC_0001.JPEG' \
  --header 'x-api-key: $IMAGEN_API_KEY'
import httpx, os

async with httpx.AsyncClient() as http:
    resp = await http.get(
        f"https://api.imagen-ai.com/v1/i2i/projects/{project_uuid}/get_download_link",
        params={"file_name": "DSC_0001.JPEG"},
        headers={"x-api-key": os.environ["IMAGEN_API_KEY"]},
    )
    link = resp.json()["data"]
const url = new URL(
  `https://api.imagen-ai.com/v1/i2i/projects/${projectUuid}/get_download_link`
);
url.searchParams.set('file_name', 'DSC_0001.JPEG');
const res = await fetch(url, { headers: { 'x-api-key': process.env.IMAGEN_API_KEY! } });
const { data } = await res.json();
import (
    "net/http"
    "net/url"
    "os"
)

u := "https://api.imagen-ai.com/v1/i2i/projects/" + projectUUID +
    "/get_download_link?file_name=" + url.QueryEscape("DSC_0001.JPEG")
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;

var uri = URI.create("https://api.imagen-ai.com/v1/i2i/projects/" + projectUuid
    + "/get_download_link?file_name="
    + URLEncoder.encode("DSC_0001.JPEG", StandardCharsets.UTF_8));
var request = HttpRequest.newBuilder()
    .uri(uri)
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .GET()
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects/#{project_uuid}/get_download_link")
uri.query = URI.encode_www_form(file_name: 'DSC_0001.JPEG')
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
link = JSON.parse(res.body)['data']
<?php
$url = 'https://api.imagen-ai.com/v1/i2i/projects/' . $projectUuid . '/get_download_link?'
  . http_build_query(['file_name' => 'DSC_0001.JPEG']);
$ch = curl_init($url);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
]);
$response = curl_exec($ch);
curl_close($ch);
$link = json_decode($response, true)['data'];
using System.Net.Http;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var response = await client.GetAsync(
    $"https://api.imagen-ai.com/v1/i2i/projects/{projectUuid}/get_download_link?file_name={Uri.EscapeDataString("DSC_0001.JPEG")}");
var json = await response.Content.ReadAsStringAsync();

Override an output file

To replace an output file after a manual touch-up, request a presigned PUT URL into the output bucket and PUT the new bytes. This endpoint is available only after the project reaches Completed.

GET /v1/i2i/projects/PROJECT_UUID/get_upload_link?file_name=DSC_0001.JPEG
# 1. Request a presigned PUT URL into the output bucket
curl 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID/get_upload_link?file_name=DSC_0001.JPEG' \
  --header 'x-api-key: $IMAGEN_API_KEY'

# 2. PUT the new bytes to that link with an empty Content-Type (see Uploading)
curl -X PUT "<presigned_upload_link>" \
  -H 'Content-Type;' \
  --upload-file DSC_0001.JPEG
import httpx, os

async with httpx.AsyncClient() as http:
    link = (await http.get(
        f"https://api.imagen-ai.com/v1/i2i/projects/{project_uuid}/get_upload_link",
        params={"file_name": "DSC_0001.JPEG"},
        headers={"x-api-key": os.environ["IMAGEN_API_KEY"]},
    )).json()["data"]

    with open("DSC_0001.JPEG", "rb") as f:
        await http.put(link, content=f.read(), headers={"Content-Type": ""})
import { readFile } from 'fs/promises';

const url = new URL(
  `https://api.imagen-ai.com/v1/i2i/projects/${projectUuid}/get_upload_link`
);
url.searchParams.set('file_name', 'DSC_0001.JPEG');
const { data: link } = await (await fetch(url, {
  headers: { 'x-api-key': process.env.IMAGEN_API_KEY! },
})).json();

// Content-Type must be empty for the presigned PUT
await fetch(link, {
  method: 'PUT',
  headers: { 'Content-Type': '' },
  body: await readFile('DSC_0001.JPEG'),
});
import (
    "bytes"
    "encoding/json"
    "net/http"
    "net/url"
    "os"
)

u := "https://api.imagen-ai.com/v1/i2i/projects/" + projectUUID +
    "/get_upload_link?file_name=" + url.QueryEscape("DSC_0001.JPEG")
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))
resp, _ := http.DefaultClient.Do(req)
var result struct{ Data string `json:"data"` }
json.NewDecoder(resp.Body).Decode(&result)
resp.Body.Close()

content, _ := os.ReadFile("DSC_0001.JPEG")
put, _ := http.NewRequestWithContext(ctx, http.MethodPut, result.Data, bytes.NewReader(content))
put.Header.Set("Content-Type", "")
http.DefaultClient.Do(put)
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;

var client = HttpClient.newHttpClient();
var linkUri = URI.create("https://api.imagen-ai.com/v1/i2i/projects/" + projectUuid
    + "/get_upload_link?file_name="
    + URLEncoder.encode("DSC_0001.JPEG", StandardCharsets.UTF_8));
var linkResp = client.send(HttpRequest.newBuilder().uri(linkUri)
    .header("x-api-key", System.getenv("IMAGEN_API_KEY")).GET().build(),
    HttpResponse.BodyHandlers.ofString());
String uploadLink = parseData(linkResp.body());

client.send(HttpRequest.newBuilder().uri(URI.create(uploadLink))
    .header("Content-Type", "")
    .PUT(HttpRequest.BodyPublishers.ofFile(Path.of("DSC_0001.JPEG"))).build(),
    HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

link_uri = URI("https://api.imagen-ai.com/v1/i2i/projects/#{project_uuid}/get_upload_link")
link_uri.query = URI.encode_www_form(file_name: 'DSC_0001.JPEG')
link_req = Net::HTTP::Get.new(link_uri)
link_req['x-api-key'] = ENV['IMAGEN_API_KEY']
link = JSON.parse(
  Net::HTTP.start(link_uri.hostname, link_uri.port, use_ssl: true) { |h| h.request(link_req) }.body
)['data']

put_uri = URI(link)
put_req = Net::HTTP::Put.new(put_uri)
put_req['Content-Type'] = ''
put_req.body = File.binread('DSC_0001.JPEG')
Net::HTTP.start(put_uri.hostname, put_uri.port, use_ssl: true) { |h| h.request(put_req) }
<?php
$linkUrl = 'https://api.imagen-ai.com/v1/i2i/projects/' . $projectUuid . '/get_upload_link?'
  . http_build_query(['file_name' => 'DSC_0001.JPEG']);
$ch = curl_init($linkUrl);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
]);
$link = json_decode(curl_exec($ch), true)['data'];
curl_close($ch);

$ch = curl_init($link);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CUSTOMREQUEST => 'PUT',
  CURLOPT_HTTPHEADER => ['Content-Type:'],
  CURLOPT_POSTFIELDS => file_get_contents('DSC_0001.JPEG'),
]);
curl_exec($ch);
curl_close($ch);
using System.Net.Http;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var linkJson = await client.GetStringAsync(
    $"https://api.imagen-ai.com/v1/i2i/projects/{projectUuid}/get_upload_link?file_name={Uri.EscapeDataString("DSC_0001.JPEG")}");
var link = JsonDocument.Parse(linkJson).RootElement.GetProperty("data").GetString();

var content = new ByteArrayContent(await File.ReadAllBytesAsync("DSC_0001.JPEG"));
content.Headers.ContentType = null; // empty Content-Type for the presigned PUT
await client.PutAsync(link, content);

List your Smart Editing projects

Browse your Smart Editing projects. Results are paginated and support filtering by archive status.

GET /v1/i2i/projects?size=20&page=0&is_archived=false

Query parameters: size (1-100, default 20), page (>= 0, default 0), is_archived (default false).

curl 'https://api.imagen-ai.com/v1/i2i/projects?size=20&page=0&is_archived=false' \
  --header 'x-api-key: $IMAGEN_API_KEY'
import httpx, os

async with httpx.AsyncClient() as http:
    resp = await http.get(
        "https://api.imagen-ai.com/v1/i2i/projects",
        params={"size": 20, "page": 0, "is_archived": "false"},
        headers={"x-api-key": os.environ["IMAGEN_API_KEY"]},
    )
    data = resp.json()["data"]
    projects, pagination = data["projects"], data["pagination"]
const url = new URL('https://api.imagen-ai.com/v1/i2i/projects');
url.searchParams.set('size', '20');
url.searchParams.set('page', '0');
url.searchParams.set('is_archived', 'false');
const res = await fetch(url, { headers: { 'x-api-key': process.env.IMAGEN_API_KEY! } });
const { data } = await res.json();
import (
    "net/http"
    "os"
)

req, _ := http.NewRequestWithContext(ctx, http.MethodGet,
    "https://api.imagen-ai.com/v1/i2i/projects?size=20&page=0&is_archived=false", nil)
req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
import java.net.URI;
import java.net.http.*;

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.imagen-ai.com/v1/i2i/projects?size=20&page=0&is_archived=false"))
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .GET()
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects")
uri.query = URI.encode_www_form(size: 20, page: 0, is_archived: false)
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)['data']
<?php
$url = 'https://api.imagen-ai.com/v1/i2i/projects?'
  . http_build_query(['size' => 20, 'page' => 0, 'is_archived' => 'false']);
$ch = curl_init($url);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true)['data'];
using System.Net.Http;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var response = await client.GetAsync(
    "https://api.imagen-ai.com/v1/i2i/projects?size=20&page=0&is_archived=false");
var json = await response.Content.ReadAsStringAsync();
{
  "data": {
    "projects": [
      {
        "project_id": 42,
        "project_uuid": "65f3a1c2d4e5f6a7b8c9d0e1",
        "name": "Real Estate Listing 1734567890",
        "status": "Completed",
        "created_at": "2026-05-31T10:42:11Z",
        "number_of_images": 42,
        "profile": null,
        "ai_tools": ["hdr_merge", "perspective_correction"],
        "customer_reference_id": 1234,
        "thumbnail_src": "https://<s3-presigned>",
        "export_status": null
      }
    ],
    "pagination": { "total": 137, "size": 20, "page": 0 }
  }
}